Final class
When a class is defined as a final class, it means that the class cannot be inherited by other classes, that is, it cannot be used after extends. Otherwise you will get an error during compilation.
package com.iderzheng.finalkeyword; public final class FinalClass {} // Error: cannot inherit from finalclass PackageClass extends FinalClass {} Java supports defining class as final, which seems to violate the basic principles of object-oriented programming. However, on the other hand, the enclosed class also ensures that all methods of the class are fixed and there will be no subclass overrides to be loaded dynamically. This provides more possibilities for the compiler to optimize. The best example is String, which is the final class. The Java compiler can directly turn string constants (those contained in double quotes) into String objects, and at the same time directly optimize the operator + operation into new constants, because final modification ensures that no subclasses return different values for splicing operations.
For all different class definitions - top-level classes (global or package visible), nested classes (internal or static nested classes) can be modified with final. However, in general, final is mostly used to modify classes defined as public, because for non-global classes, access modifiers have restricted their visibility, and it is already difficult to inherit these classes, so there is no need to add a layer of final restrictions.
It is also mentioned that although anonymous classes cannot be inherited, they are not limited to final by the compiler.
import java.lang.reflect.Modifier; public class Main { public static void main(String[] args) { Runnable anonymous = new Runnable() { @Override public void run() { } }; System.out.println(Modifier.isFinal(anonymous.getClass().getModifiers())); }}Output:
false
Final Method
Closely related to the concept of inheritance is polymorphism, which involves the difference between the concepts of overriding and hiding (for convenience, the following are collectively called "rewriting"). However, unlike the method definition in C++ whether virtual keyword is added to the subclass, whether the method of subclass signature is overwritten or hidden, in Java, subclasses use the same method signature to overwrite the parent class method, which will form a hidden class method (static method), while object methods (non-static method) only overwrite. Since Java allows direct access to class methods through objects, Java does not allow class methods and object methods to have the same signature in the same class.
The final class defines that the entire class cannot be inherited, and it also means that all methods in the class cannot be covered and hidden by subclasses. When the class is not modified by final, some methods can still be modified using final to prevent these methods from being rewritten by subclasses.
Similarly, such a design destroys object-oriented polymorphism, but the final method can ensure the determinism of its execution, thus ensuring the stability of method calls. In some framework designs, some implemented methods of abstract classes are often seen to be limited to final, because some driver code in the framework will rely on these methods to achieve the established goals, so there are no subclasses that cover it.
The following example shows the role of final modification in different types of methods:
package com.iderzheng.other; public class FinalMethods { public static void publicStaticMethod() { } public final void publicFinalMethod() { } public static final void publicStaticFinalMethod() { } protected final void protectedFinalMethod() { } protected static final void protectedStaticFinalMethod() { } final void finalMethod() { } static final void staticFinalMethod() { } private static final void privateStaticFinalMethod() { } private final void privateFinalMethod() { }}package com.iderzheng.finalkeyword; import com.iderzheng.other.FinalMethods; public class Methods extends FinalMethods { public static void publicStaticMethod() { } // Error: cannot override public final void publicFinalMethod() { } // Error: cannot override public static final void publicStaticFinalMethod() { } // Error: cannot override public static final void publicStaticFinalMethod() { } // Error: cannot override public static final void publicStaticFinalMethod() { } // Error: cannot override protected final void protectedFinalMethod() { } // Error: cannot override protected static final void protectedStaticFinalMethod() { } final void finalMethod() { } static final void staticFinalMethod() { } private static final void privateStaticFinalMethod() { } private final void privateFinalMethod() { }} First of all, note that in the above example, FinalMethods and Methods are defined under different packages. For the first publicStaticMethod, the subclass successfully rewrites the static method of the parent class, but because it is a static method, what happens is actually "hidden". Specifically, calling Methods.publicStaticMethod() will execute the implementation in the Methods class. When calling FinalMethods.publicStaticMethod(), the implementation will not occur with the polymorphic loading of the subclass, but will directly use the implementation of FinalMethods. Therefore, when using subclasses to access methods, the visibility of the methods signed by the parent class is hidden.
For the global method publicFinalMethod, as described in the final modification method, the subclass is forbidden to overwrite it, and an exception will be thrown at compile time. However, the method name is the same in the subclass, but it has a parameter, such as: publicFinalMethod(String x) is OK, because this is the synchronous method signature.
In Intellij, the IDE shows a warning to the publicStaticFinalMethod: 'static' method declared 'final'. It seems redundant to it, but from the example, it can be seen that final also prohibits subclass definitions from static methods to hide it. In actual development, the behavior of defining the same static methods of subclasses and parent classes is extremely desirable, because hidden methods require developers to pay attention to the use of different class names to define different effects, which is easy to cause errors. Moreover, within the class, you can call static methods directly without using the class name. When the developer inherits again, he may not notice the hidden existence. By default, when using the parent class method, he will find that it is not the expected result. Therefore, static methods should be final by default and should not be hidden, so IDE thinks it is an unnecessary modification.
The protected modification and public modification methods in the parent class are visible to the subclass, so the situation of final modification of protected methods is the same as that of public methods. It is to be mentioned that in actual development, protected static methods are generally rarely defined because such methods are too practical.
For the parent class package method, subclasses under different packages are invisible. The private method has been customized and only the parent class can access it. So the compiler allows subclasses to define the same method. But this does not form override or hide, because the parent class has hidden these methods through modifiers, not caused by rewriting of subclasses. Of course, if the subclass and the parent class are in the same package, then the situation will be the same as the previous public and protected.
Why is the final method efficient?
The final method will use the embedded mechanism to optimize inline during compilation. Inline optimization refers to: calling function code replacement directly during compilation, rather than calling functions at runtime. Inline needs to know which function to use in the end when compiling. Obviously, it is not possible to use it without final. Non-final methods may be rewritten in subclasses. Due to possible polymorphism, the compiler cannot determine the true type of the object to call the method in the future during the compilation stage, and it cannot determine which method to call.
Final Variable
Simply put, the final variable in Java can only and must be initialized once, and then the variable is bound to the value. However, this assignment does not necessarily need to be initialized immediately when the variable is defined. Java also supports different results for final variables through conditional statements, but the variable can only be assigned once in any case.
However, Java's final variable is not an absolute constant, because Java's object variables are only reference values, so final just means that the reference cannot be changed, and the content of the object can still be modified. Compared to C/C++ pointers, it is more like type * const variable than type const * variable.
Java variables can be divided into two categories: local variables (Local Variable) and class member variables (Class Field). The following is a code to introduce their initialization situation separately.
Local Variable
Local variables mainly refer to variables defined in methods. They will disappear and become inaccessible after the method. There is a special case that can be divided into: function parameters. For this case, its initialization is bound to the parameters passed in when the function is called.
For other local variables, they are defined in the method, and their values can be conditionally initialized:
public String method(final boolean finalParam) { // Error: final parameter finalParam may not be assigned // finalParam = true; final Object finalLocal = finalParam ? new Object() : null; final int finalVar; if (finalLocal != null) { finalVar = 21; } else { finalVar = 7; } // Error: variable finalVar might already have been assigned // finalVar = 80; final String finalRet; switch (finalVar) { case 21: finalRet = "me"; break; case 7: finalRet = "she"; break; default: finalRet = null; } return finalRet;} From the above example, it can be seen that the function parameters modified by final cannot be assigned a new value, but other final local variables can be assigned a value in a conditional statement. This also provides a certain flexibility for final.
Of course, all conditions in the conditional statement should contain assignments to final local variables, otherwise you will get an error that the variable may not be initialized.
public String method(final Object finalParam) { final int finalVar; if (finalParam != null) { finalVar = 21; } final String finalRet; // Error: variable finalVar might not have been initialized switch (finalVar) { case 21: finalRet = "me"; break; case 7: finalRet = "she"; break; } // Error: variable finalRet might not have been initialized return finalRet;} In theory, local variables are not necessary to be defined as final, and a reasonable design method should be able to maintain local variables well. It is just that when using anonymous functions to make closures in Java methods, Java requires that the referenced local variable must be defined as final:
public Runnable method(String string) { int integer = 12; return new Runnable() { @Override public void run() { // ERROR: needs to be declared final System.out.println(string); // ERROR: needs to be declared final System.out.println(integer); } };}Class Field
Class member variables can actually be divided into two types: static and non-static. For static class member variables, because they are related to classes, in addition to being directly initialized at definition time, they can also be placed in a static block, and using the latter can execute more complex statements:
package com.iderzheng.finalkeyword; import java.util.HashSet;import java.util.LinkedHashSet;import java.util.Set; public class StaticFinalFields { static final int STATIC_FINAL_INIT_INLINE = 7; static final Set<Integer> STATIC_FINAL_INIT_STATIC_BLOCK; /** Static Block **/ static { if (System.currentTimeMillis() % 2 == 0) { STATIC_FINAL_INIT_STATIC_BLOCK = new HashSet<>(); } else { STATIC_FINAL_INIT_STATIC_BLOCK = new LinkedHashSet<>(); } STATIC_FINAL_INIT_STATIC_BLOCK.add(7); STATIC_FINAL_INIT_STATIC_BLOCK.add(21); }}There are also non-static blocks in Java that can initialize non-static member variables, but for these variables, they are often placed in the constructor for initialization. Of course, it is necessary to ensure that each final variable is initialized once in the constructor. If other constructors are called through this(), these final variables can no longer be assigned in the constructor.
package com.iderzheng.finalkeyword; public class FinalFields { final long FINAL_INIT_INLINE = System.currentTimeMillis(); final long FINAL_INIT_BLOCK; final long FINAL_INIT_CONSTRUCTOR; /** Initial Block **/ { FINAL_INIT_BLOCK = System.nanoTime(); } FinalFields() { this(217); } FinalFields(boolean bool) { FINAL_INIT_CONSTRUCTOR = 721; } FinalFields(long init) { FINAL_INIT_CONSTRUCTOR = init; }}When final is used to modify classes (Class) and methods (Method), it mainly affects object-oriented inheritance. Without inheritance, there will be no dependence on the code of the subclass on the parent class. Therefore, when modifying the code during maintenance, it does not need to consider whether the implementation of the subclass will be destroyed, which makes it more convenient. When it is used on a variable, Java ensures that the variable value will not be modified. If further design ensures that the members of the class cannot be modified, then the entire variable can be turned into a constant, which is very beneficial for multi-threaded programming. Therefore, final has a very good effect on code maintenance.