The final keyword
The summary of the final
Final can modify variables, methods, and classes to indicate that the modified content will not be changed once the value is assigned. For example, the String class is a class of final type.
Specific usage scenarios for final
Final can modify variables, methods, and classes, meaning that the use of final covers almost every part of Java. Here are the locations of lock modification: variables, methods, and classes.
Final decorates a member variable
Public class FinalExample {private final int num=6; Private final String STR; private final String STR; private final String STR; Private final static String name; private final static String name; private final double score; private final char ch; //private final char ch2; // Compiler error :TODO: because the constructor, initialization block, and declaration are not assigned {// instance variable initialization block initial value ch='a';
}
static {
name="aaaaa";
}
public FinalExample(){ //num=1; Compile error: Score =90.0 cannot be modified after an assignment; } public voidch2(){ //ch2='c'; // compilation error: instance method cannot assign value to final variable}}Copy the code
-
Class variables: You must specify an initial value either in a static initialization block or when declaring a class variable, and only in one of these two places
-
Instance variable: It is necessary to declare the instance variable in a non-static initializer block, or to specify an initial value in a constructor, and only in these three places
Final decorates local variables
Final local variables are explicitly initialized by the programmer. If a final local variable has already been initialized, it cannot be changed again later. If a final variable has not been initialized, it can be assigned.
public void test(){ final int a=1; //a=2; // compilation error: Final local variables have already been initialized and cannot be changed later}Copy the code
Final Base data type VS Final reference data type
If final modifies data of a primitive data type and cannot be changed again once assigned, what if final is a reference data type? Can the referenced object be changed?
public class FinalExample2 { private static class Person { private String name; private int age; public void setName(String name) { this.name = name;
} public String getName() { return name;
} public void setAge(int age) { this.age = age;
} public int getAge() { return age;
} public Person(String name, int age) { this.name=name; this.age = age;
} @Override
public String toString() { StringBuilder res=new StringBuilder();
res.append("[").append("name="+name+",age="+age).append("]"); return res.toString();
}
} private static final Person person=new Person(Little Leo,23); public static void main(String[] args) { System.out.println(person);
person.setAge(24); System.out.println(person);
}
}Copy the code
Output result:
[name=小 plum,age=23] [name=小 plum,age=24]Copy the code
When we change the property of person, the reference datatype variable to the final modifier, to 24, we can operate successfully. From this experiment, we can see that when final modifies a primitive variable, the primitive variable cannot be reassigned, so the primitive variable cannot be changed. For reference type variables, it only stores a reference, and final only guarantees that the address referenced by the reference type variable will not change, that is, the object will always be referenced, but the object property can be changed.
Macro variables
Using the immutable nature of a final variable, the variable becomes a “macro variable,” that is, a constant, if the following three conditions are met.
-
Modify with the final modifier
-
The initial value is specified when the final variable is defined;
-
This initial value is uniquely determined at compile time
Note: Where the macro variable is used elsewhere in the program, the compiler replaces it directly with the value of the variable.
Final decoration method
Rewrite (Override)
Methods modified by final cannot be overridden by subclasses. For example, in Object, the getClass() method is final and we cannot override it, but the hashCode() method is not final and we can override the hashCode() method.
Overloading (phrase)
Methods modified by final can be overridden
Final modifier class
When a class is final, it cannot be inherited by subclasses. Subclass inheritance often overrides the methods of the parent class and changes the attributes of the parent class, which is a security risk. Therefore, final decoration can be used when a class does not want to be inherited.
Immutable classes
Final is often used for immutable classes. Let’s first look at what immutable classes are:
-
Use the private and final modifiers to modify the member variables of the class
-
Provides a constructor with arguments used to initialize a member variable of a class
-
Provide getters only for member variables of the class, not setters, because regular methods cannot modify finA-modified member variables
-
Override the hashCode() and equals() methods of the Object class if necessary. Ensure that equals() is used to determine that the hashCode values of two identical objects are equal.
The eight wrapper classes and String classes provided in the JDK are immutable.
Final domain reordering rules
Final is the base type
public class FinalDemo { private int a; Private final int b; // Final field -->int Basic type private static FinalDemo; // Reference type, but not final modifier publicFinalDemo() { a = 1; // 1. Write common field b = 2; // write final fields} public static voidwriter() {
finalDemo = new FinalDemo();
} public static void reader() { FinalDemo demo = finalDemo; Int a = demo.a; Int b = demo.b; //5. Read final fields}}Copy the code
Suppose thread A is executing writer() and thread B is executing reader().
Write the final field reorder
Reorder rules for writing final fields: The reorder rule forbids writing final fields outside the constructor. The implementation of this rule has two main aspects:
-
The JMM forbids the compiler from reordering writes to final fields outside the constructor
-
The compiler inserts a StoreStore barrier after the final field is written, but before the constructor return. This barrier prevents the processor from reordering writes to final fields outside the constructor. (See description of StoreStore Barriers: at Store1; Insert StoreStore between Store2, making sure Store1 is visible to other processors (refresh memory) before Store2 and all subsequent store instructions)
The Writer method actually does two things:
-
A FinalDemo object is constructed
-
Assign this object to the member variable finalDemo
Possible execution sequence diagrams are as follows:
There is no data dependency between a and B, the common field (common variable) A may be reordered outside the constructor, and thread B may read the value of the common variable before it was initialized (zero), which may cause an error.
The final field variable B, according to the reordering rule, prevents the final modified variable B from being reordered outside the constructor, so that B can be assigned correctly and thread B can read the initialized value of the final variable.
Therefore, reordering rules that write final fields ensure that an object’s final field is properly initialized before its reference is visible to any thread. Normal domains do not have this guarantee, as in the example above, thread B might be an improperly initialized finalDemo object.
Read the final field reordering rules
Read final field reordering: The JMM disallows the reordering of the first read of an object reference and the first read of the final field that the object contains within a thread. (Note that this rule applies only to the processor.) The processor inserts a LoadLoad barrier before reading a final field operation. In fact, there is an indirect dependency between the reference to the read object and the final field that reads the object, and typically the processor does not reorder these two operations. However, some processors reorder, so this disallow reorder rule is for those processors.
The read method consists of three operations:
-
First read the reference variable finalDemo
-
The first read is the ordinary field A of the reference variable finalDemo
-
First read final field B of the reference variable finalDemo
Assuming that thread A’s write process is not reordered, thread A and thread B have A possible execution sequence as follows:
The normal field of the read object is reordered before the reference of the read object. Thread B is reading the normal field variable of the read object before the reference of the read object. This is obviously the wrong operation.
A read ina final field “qualifies” that a reference to the object has been read before reading a final field variable, thus avoiding this situation.
Therefore, reordering rules for reading final fields ensure that the reference to the object containing the final field of an object is read before reading the final field of the object.
Final is the reference type
public class FinalReferenceDemo { final int[] arrays; // Arrays is the reference type private FinalReferenceDemo FinalReferenceDemo; publicFinalReferenceDemo() {
arrays = new int[1]; //1
arrays[0] = 1; //2
} public void writerOne() {
finalReferenceDemo = new FinalReferenceDemo(); //3
} public void writerTwo() {
arrays[0] = 2; //4
} public void reader() { if (finalReferenceDemo != null) { //5
int temp = finalReferenceDemo.arrays[0]; //6
}
}
}Copy the code
Writes to the member field of a final modified object
According to reference data types, writing for the compiler and final domain in highly increased such constraints: in the constructor of a final modified objects to members of the domain, and then in the constructor to this is constructed object reference is assigned to a reference variable, the two operations cannot be reorder. Note that the word “increment” means that the previous reorder for the final base data type is still used here.
Thread A executes the wirterOne method, thread B executes the writerTwo method, and thread C executes the Reader method. The following figure shows a case of this execution sequence:
Writes to final fields prohibit reordering out of the constructor, so 1 and 3 cannot be reordered. 2 and 3 cannot be reordered because writes to the member field of a reference object ina final field cannot be reordered with assigning the constructed object to a reference variable outside the constructor.
Reads the member field of a final modified object
The JMM ensures that thread C will at least see writerThread A’s writes to the member fields of final referenced objects (i.e., arrays[0] = 1), while writerthread B’s writes to array elements may or may not be seen. The JMM does not guarantee that writes from thread B are visible to thread C, and there is a data race between thread B and thread C, where the outcome is unpredictable. If you want visibility, you can use locking or volatile.
Final Summary of reordering
Final written | Final domain to read | |
---|---|---|
Basic data types | Disallow final field writes and constructor reordering, that is, disallow final field writes to be reordered outside the constructor to ensure that all final fields of an object are initialized by the time the object is visible to all threads | Disallows reordering of references to objects that are read for the first time and of final fields that are read for that object, ensuring that references to objects containing that final field are read before reading the final field of an object |
Reference data type | Additional constraint: Writes to the member field of a final-modified object within a constructor and subsequent assignments of a reference to the constructed object to a reference variable outside the constructor cannot be reordered |
Object overflow
Object overflow: The erroneous publication of an object to be seen by other threads before it has been constructed.
/*** object overflow example */public ThisEscape {public ThisEscape(EventSourcesource) {source. RegisterListener (newEventListener() {public void onEvent(Event e) {doSomething (e); }}); } voiddoSomething(Event e) {}}Copy the code
This will cause this to escape. By escape, a reference is published when it shouldn’t be.
In this example, when we instantiate the ThisEscape object, the source registerListener method is called, and a thread is started that holds the ThisEscape object (calling the object’s doSomething method), ThisEscape object has not been instantiated yet (no reference has been returned), so we say that a thisreference escape is caused, i.e. the action of instantiating ThisEscape object has not been completed, but has exposed the reference to the object.
Correct construction process:
Public class SafeListener {private final EventListener Listener; privateSafeListener() {listener = newEventListener() {public void onEvent(Event e) {doSomething (e); }}; } public static SafeListener newInstance(EventSourcesourceSafeListener safe = new SafeListener(); source.registerListener(safe.listener);returnSafe; } voiddoSomething (the Event e) {}Copy the code
After the SafeListener object is constructed (via the constructor), we start the listener thread, ensuring that the SafeListener object is the SafeListener object to be used after construction is complete.
Conclusion:
-
The this reference should escape from the thread only if the constructor returns.
-
The constructor can save this reference somewhere, as long as other threads don’t use it until the constructor completes.
How final is implemented
Writing to a final field requires the compiler to insert a StoreStore barrier after the final field is written and before the constructor returns.
Reordering rules for reading final fields require the compiler to insert a LoadLoad barrier before reading operations in final fields.
If X86 processing is taken as an example, X86 does not reorder write-write, so the StoreStore barrier can be omitted. Since operations that have indirect dependencies are not reordered, the LoadLoad barrier required to read final fields is also omitted on X86 processors. In other words, for X86, the memory barrier for reading/writing final domains is omitted! It depends on what processor it is.
-
Note:
Writing reordering rules for final fields ensures that when we use an object reference, the object’s final field has already been initialized in the constructor. However, there is a prerequisite: in the constructor, the object being constructed cannot be made visible to other threads, that is, the object reference cannot “overflow” in the constructor.
public class FinalReferenceEscapeDemo { private final int a; private FinalReferenceEscapeDemo referenceDemo; public FinalReferenceEscapeDemo() {
a = 1; //1
referenceDemo = this; //2
} public void writer() { new FinalReferenceEscapeDemo();
} public void reader() { if (referenceDemo != null) { //3
int temp = referenceDemo.a; //4
}
}
}Copy the code
Suppose one thread, A, executes writer and another thread executes reader. Since there is no data dependency between operations 1 and 2 in the constructor, 1 and 2 can be reordered, executing 2 first, at which point the reference object referenceDemo is an incomplete initialized object that thread B fails to read. The final field write reordering rule is still met, though: by the time a reference object is visible to all threads, its final field has been fully initialized. However, if the reference object “this” escapes, the code still has thread-safety issues.