Finally, finally, finalize?

Interview question: the difference between final, finally and finalize

background knowledge

final/finally is no longer used at work almost all the time, so even if there is no systematic combing of this question, some contents can be answered.

But finalize has very little contact. Next, we analyze these keywords one by one.

Final keyword

Final keyword is not unfamiliar to everyone, but it is still lacking in order to achieve in-depth understanding. We understand the final keyword from three aspects.

  1. Basic usage of final keyword
  2. Deep understanding of final keyword
  3. Memory barrier semantics of final keyword

Basic Usage

final keyword, which can modify classes, methods and variables in Java.

  1. A class modified by final indicates that the class cannot be inherited. The member variables in the final class can be set to final as needed, and all member methods in the final modified class are implicitly specified as final methods

    When using final to decorate a class, be careful. Unless this class will not be used for inheritance in the future or for security reasons, try not to design the class as final.

    public final class FinalTest {
    
        public final String test() {
            return "true";
        }
    
    }
    

    Then create a class to inherit the class. We can find the following errors.

  2. The method modified by final indicates that the method cannot be overridden. The private method will be implicitly specified as the final method.

    class SuperClass {
        protected final String getName() {
            return "supper class";
        }
    
        @Override
        public String toString() {
            return getName();
        }
    }
    


3. The member variable modified by final is the most used place.

  • For a final variable, if it is a variable of basic data type, its value cannot be changed after initialization; The variables modified by final can indirectly realize the function of constants, and constants are global and immutable. Therefore, we can define constants by modifying variables with static and final at the same time.

  • If it is a variable of reference type, it can no longer point to another object after its initialization.

Initialization of variables modified by final

  1. Initializes the value of the property at definition time

    class SuperClass {
    
        private final String name;
    
        protected final String getName() {
            return "supper class";
        }
    
        @Override
        public String toString() {
            return getName();
        }
    }
    

    The above code will cause an error because we did not initialize the data.

    Assign a value to the name field.

  2. Assignment in construction method

    In addition to direct assignment, we can also assign values to attributes in construction methods.

    class SuperClass {
    
        private final String name;
    
        public SuperClass(String name) {
            this.name = name;
        }
    
        protected final String getName() {
            return "supper class";
        }
    
        @Override
        public String toString() {
            return getName();
        }
    }
    

    The reason why you can assign values in the constructor is that when assigning values to an ordinary member attribute, you must first instantiate the object through the constructor. Therefore, as the only access to this attribute, the JVM allows assignment to the final modified attribute in the constructor. This process does not violate the principle of final. Of course, if the property of the modified final keyword has been initialized, it can no longer be re assigned with the constructor.

Reflection breaking final rule

Based on the basic usage description of the above final keyword, you can know that the attributes of final modification are immutable.

However, the rules of final can be broken through the reflection mechanism. The code is as follows:

class SuperClass {

    private final String name = "name";

    public static void main(String[] args) throws Exception {
        SuperClass superClass = new SuperClass();
        System.out.println(superClass.name);

        Field field = superClass.getClass().getDeclaredField("name");
        field.setAccessible(true);
        field.set(superClass, "chen");
        System.out.println(field.get(superClass));
    }
}

The results are as follows:

name
chen

Knowledge point expansion

Theoretically, the above code should be written in the following way, because after modifying the name attribute in the tcc instance object through reflection, the name result should be printed directly through the instance object.

public static void main(String[] args) throws Exception {
        SuperClass superClass = new SuperClass();
        System.out.println(superClass.name);

        Field field = superClass.getClass().getDeclaredField("name");
        field.setAccessible(true);
        field.set(superClass, "chen");
        //System.out.println(field.get(superClass));
        System.out.println(superClass.name);
}

However, after the actual output results, it is found that the results printed by superClass.name have not changed?

The reason is that the JVM optimizes the String of final type through the in-depth optimization mechanism during compilation. The String will be treated as a constant during compilation, so that the print result will not change.

In order to avoid the impact of this deep optimization, we can also modify the above code to the following form

	//Deal with the deep optimization brought by jvm
    private final String name=(null == null ? "name" : "");


    public static void main(String[] args) throws Exception {
        SuperClass superClass = new SuperClass();
        System.out.println(superClass.name);

        Field field = superClass.getClass().getDeclaredField("name");
        field.setAccessible(true);
        field.set(superClass, "chen");
        //System.out.println(field.get(superClass));
        System.out.println(superClass.name);
    }

The printing results are as follows:

name
chen

Reflection cannot modify variables modified by both final and static

Modify the above code as follows.

private static final String name=(null == null ? "name" : "");

The following error messages will appear:

Exception in thread "main" java.lang.IllegalAccessException: Can not set static final java.lang.String field com.example.javastudy.interview.SuperClass.name to java.lang.String
	at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)
	at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80)
	at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set(UnsafeQualifiedStaticObjectFieldAccessorImpl.java:77)
	at java.lang.reflect.Field.set(Field.java:764)
	at com.example.javastudy.interview.SuperClass.main(SuperClass.java:26)

Can attributes modified by both final and static be modified? The answer is yes!

The modification code is as follows:

//Deal with the deep optimization brought by jvm
private static final String name = (null == null ? "name" : "");


public static void main(String[] args) throws Exception {
    SuperClass superClass = new SuperClass();
    System.out.println(superClass.name);

    Field field = superClass.getClass().getDeclaredField("name");
    field.setAccessible(true);

    // Remove the final keyword to modify the static and final attributes
    Field modifiers = field.getClass().getDeclaredField("modifiers");
    modifiers.setAccessible(true);
    modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);

    field.set(superClass, "chen");

    modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);

    System.out.println(superClass.name);
}

The specific idea is to remove the final keyword through reflection by modifying the name attribute of the modified final keyword, and then modify the name attribute through reflection. After the modification is successful, use the following code to add back the final keyword

		modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);

        field.set(superClass, "chen");

        modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);

Why can local inner classes and anonymous inner classes only access final variables

Before understanding this problem, we add the following code to the previous class:

public void test(final int b) {
        final int a = 10;
        new Thread(){
            public void run() {
                System.out.println(a);
                System.out.println(b);
            };
        }.start();
    }

After this code is compiled, two files are generated: SuperClass.class and SuperClass .class (anonymous inner class)

[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-swcvksnr-1636088510015) (the difference between final, finally and finalize. assets/image-20211105112752171.png)]

Take a look at the class SuperClass .class through decompilation

class SuperClass$1 extends Thread {
    SuperClass$1(SuperClass this$0, int var2, int var3) {
        this.this$0 = this$0;
        this.val$a = var2;
        this.val$b = var3;
    }

    public void run() {
        System.out.println(this.val$a);
        System.out.println(this.val$b);
    }
}

We can see that the constructor of the anonymous inner class FinalExample contains three parameters, one is a reference to the external class object, and the other two are int variables. Obviously, here is to pass the formal parameter B and constant a in the variable test method in the form of parameters to assign and initialize the copy in the anonymous inner class (the copy of variables A and b).

In other words, the variables A and b accessed in the run method are a copy of the local variables A and b. why is it so designed?

In the test method, it is possible that the execution of the test method has ended and the declaration cycle of a and b has also ended, but the anonymous inner class Thread may not have been executed yet, so it will be a problem to continue to use local variables A and b in the run method in Thread. But what should we do to achieve such an effect? Therefore, Java uses replication to solve this problem.

However, there is still a problem that the data of the member variable in the test method is inconsistent with the copy of the member variable in the anonymous inner class Thread?

In this way, the original intention and requirements can not be achieved. In order to solve this problem, the java compiler restricts variables a and b to final variables and does not allow changes to variables a and b (for variables of reference type, it is not allowed to point to new objects). In this way, the problem of data inconsistency can be solved.

In addition, if this is allowed, the jvm will implicitly add the final keyword to a and b.

public void test(int b) {
  int a = 10;
  new Thread(){
    public void run() {
      System.out.println(a);
      System.out.println(b);
    };
  }.start();
}

final prevent instruction rearrangement

The final keyword can also prevent the visibility problem caused by instruction reordering;

For final variables, both the compiler and the processor follow two reordering rules:

  • In the constructor, the writing of a final variable and the subsequent assignment of the reference of the constructed object to a variable cannot be reordered between the two operations.
  • When reading an object containing a final variable for the first time and then reading the final variable for the first time, the two operations cannot be reordered.

In fact, these two rules are for writing and reading final variables.

  1. The written reordering rule can ensure that the final variable of the object has been correctly initialized before the object reference is visible to any thread, while ordinary variables do not have this guarantee;
  2. The read reordering rule can ensure that the reference of an object will be read before reading the final variable of the object. If the read reference is not empty, according to the above write rules, the final variable of the object must be initialized, so that the correct variable value can be read.

If the type of the final variable is reference type, the writing of the member field of an object referenced by final in the constructor and the subsequent assignment of the reference of the constructed object to a reference variable outside the constructor cannot be reordered between the two operations. In fact, this is also to ensure that the final variable can be initialized correctly before it is visible to other threads.

Benefits of the final keyword

  • The final keyword improves performance. Both JVM and Java applications cache final variables (actually constant pools)
  • The final variable can be shared safely in a multithreaded environment without additional synchronization overhead

Problem solving

Interview question: have you used the final keyword? What does it do

Answer: the final keyword is immutable. It can be modified in classes, methods and member variables.

  1. If it is decorated on a class, it means that the class is not allowed to be inherited
  2. Modifier on a method to indicate that the method cannot be overridden
  3. Modified on a variable, which means that the variable cannot be modified, and the JVM will implicitly define it as a constant.

In addition, the keyword modified by final can also avoid the visibility problem caused by instruction reordering. The reason is that final follows two reordering rules

  1. In the constructor, the writing of a final variable and the subsequent assignment of the reference of the constructed object to a variable cannot be reordered between the two operations.
  2. When reading an object containing a final variable for the first time and then reading the final variable for the first time, the two operations cannot be reordered.

finally keyword

finally keyword is used after the try statement block. Its common form is:

try{

}catch(){

}finally{

}

The code in the finally statement block will be executed no matter whether there are exceptions in the try or catch code block, so it is generally used for cleaning up, closing links and other types of statements.

Its features:

  1. finally statements must be accompanied by try statements.
  2. The try statement cannot be used alone. It must be combined with the catch statement or finally statement.
  3. The try statement can be used alone with the catch statement, alone with the finally statement, or together with the three.

finally, practical thinking

In order to deepen your understanding of the finally keyword, let's look at the following code.

Think about it. What are the print results of the following code?

public class FinallyExample {

    public static void main(String arg[]) {
        System.out.println(getNumber(0));
        System.out.println(getNumber(1));
        System.out.println(getNumber(2));
        System.out.println(getNumber(4));
    }

    public static int getNumber(int num) {
        try {
            int result = 2 / num;
            return result;
        } catch (Exception exception) {
            return 0;
        } finally {
            if (num == 0) {
                return -1;
            }
            if (num == 1) {
                return 1;
            }
        }
    }

}

The correct answers are:

  1. -1: If num=0 is passed in, an error java.lang.arithmetexception: / by zero will be reported. Therefore, enter catch to catch the exception. Since the finally statement block must be executed, enter the finally statement block and return - 1.
  2. 1: When num=1 is passed in, the program runs normally. Since the finally statement block must be executed, enter the finally code block and get the result 1.
  3. 1: When num=2 is passed in, the program runs normally and result=1. Because the finally statement block will be executed, it enters the finally code block, but the finally statement block does not trigger the modification of the result, so the returned result is 1.
  4. 0: pass in num=4. At this time, the program runs normally, and result = 0 (because 2 / 4 = 0.5, 0 is obtained after conversion to int). Since the finally statement block must be executed, it enters the finally code block, but the finally statement block does not trigger the modification of the result, so the returned result is 0.

Under what circumstances will finally not execute

finally, is there a case that the code block will not be executed?

System.exit()

Look at the following code:

public class FinallyExample {

    public static void main(String arg[]){
        System.out.println(getNumber(0));
    }
    public static int getNumber(int num){
        try{
            int result=2/num;
            return result;
        }catch(Exception exception){
            System.out.println("Trigger abnormal execution");
            System.exit(0);
            return 0;
        }finally{
            System.out.println("implement finally Statement block");
        }
    }
}

In the catch statement block, the System.exit(0) code is added. The execution results are as follows

Trigger abnormal execution

It can be found that in this case, the finally statement block is not executed.

This method is used to end the currently running Java JVM. If status is a non-zero parameter, it indicates an abnormal exit.

  1. System.exit(0): turn off the contents of the entire virtual machine and free the memory! Exit the program normally.
  2. System.exit(1): abnormal exit program
  3. System.exit(-1): abnormal exit program

Since the current JVM has ended, the program code cannot continue to execute naturally.

Daemon thread interrupted

Let's start with the following code:

public class FinallyExample {

    public static void main(String[] args) {
        Thread t = new Thread(new Task());
        t.setDaemon(true); //Set as daemon thread
        t.start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException("the "+Thread.currentThread().getName()+" has been interrupted",e);
        }
    }

}

class Task implements Runnable {
    @Override
    public void run() {
        System.out.println("implement run()method");
        try {
            System.out.println("implement try Statement block");
            TimeUnit.SECONDS.sleep(5); //Blocking for 5s
        } catch (InterruptedException e) {
            System.out.println("implement catch Statement block");
            throw new RuntimeException("the " + Thread.currentThread().getName() + " has been interrupted", e);
        } finally {
            System.out.println("implement finally Statement block");
        }
    }
}

The operation results are as follows:

implement run()method
 implement try Statement block

From the results, it is found that the code in the finally statement block has not been executed? Why?

The feature of daemon thread is that as long as there are no non daemon threads running in the JVM, the virtual opportunity will kill all daemon threads and terminate the program. In other words, whether the daemon thread is running or not does not affect the termination of the JVM.

In virtual machines, garbage collection threads and main threads are daemon threads.

In the above running program, the execution logic is described as follows:

  1. Thread t is a daemon thread. It starts a Task to execute. The thread t executes in the main method. After sleeping for 1s, the execution of the main method ends
  2. Task is the execution task of a daemon thread, which sleeps for 5s.

Based on the characteristics of daemon threads, both main and task are daemon threads. Therefore, when the main thread finishes executing, it will not be blocked because the task thread has not finished executing. Instead, wait for 1s and end the process.

This makes the code of the Task thread not complete, but the JVM process has ended, so the finally statement block has not been executed.

finally execution order

Based on the above understanding, do you think you have mastered the finally keyword well? Let's take a look at the following question.

Recreate the class to view the bytecode.

What is the execution result of this code?

public class FinallyExample2 {

  public int add() {
    int x = 1;
    try {
      return ++x;
    } catch (Exception e) {
      System.out.println("implement catch Statement block");
      ++x;
    } finally {
      System.out.println("implement finally Statement block");
      ++x;
    }
    return x;
  }
    
  public static void main(String[] args) {
    FinallyExample2 t = new FinallyExample2();
    int y = t.add();
    System.out.println(y);
  }
    
}

The results of the above program are: 2

This result should be a little unexpected, because according to the semantics of finally, first execute the try code block, the result obtained after + + x should be 2, and then execute the finally statement block, which should be + 1 on the basis of 2, and the result is 3. Why 2?

The problems related to bytecode design here will not be described in detail. We can roughly understand that if there is a return in the try statement, the behavior of the code is as follows:

  1. If there is a return value, the return value is saved to the local variable
  2. Execute the jsr instruction and jump to the finally statement for execution
  3. After executing the finally statement, return the value previously saved in the local variable table

finalize method

The finalize method is defined in the Object class. Its method definition is as follows:

protected void finalize() throws Throwable {
}

When a class is recycled, this method may be called to.

Its usage rules are:

  1. When an object is no longer referenced by any object, GC calls the finalize() method of the object
  2. finalize() is an Object method. Subclasses can override this method to release system resources or clean up data
  3. This object can be referenced again in finalize() to avoid being recycled by GC; But the most common purpose is to do cleanup
  4. Java does not guarantee that this finalize() will be executed; However, ensure that the thread calling finalize does not hold any user visible synchronization locks.
  5. Exceptions thrown in finalize are ignored and the method terminates.
  6. When finalize is called, the JVM will again check whether the object can be accessed by the surviving thread. If not, the object will be cleared. That is, finalize can only be called once; That is, objects that override the finalize method need to go through two GC cycles to be cleared.

Question answer

Interview question: the difference between final, finally and finalize

answer:

  1. Final is used to modify classes, methods and properties. A class modified by final means that the class cannot be inherited. A property modified by final means that the property cannot be modified. A method modified by final means that the method cannot be overridden

  2. finally, it forms a complete syntax with the try statement block to represent the code block that must be executed. Of course, there are also ways to destroy its execution characteristics

    1. Through System.exit
    2. Termination of daemon thread
  3. The finalize method is a method that may be called when a class is recycled.

Tags: Java Back-end

Posted on Fri, 05 Nov 2021 00:14:26 -0400 by Arnerd