Java中的final关键字

final关键字可以修饰类、方法、变量。本质作用可以理解为“声明被修饰对象只读”,不同用法对应不同的具体解释。

final类

final类表示该类是完整(可理解为最终版本)的,禁止扩展。final标记会写入class文件的类定义中,在编译期检查,如果发现上游extends了一个final类后,则编译失败,从语法上保证不能extends一个final类。

final类经常用于不可变类的构造中:如果不使用final修饰,且有对子类可见的可变成员变量,则extends该“不可变类”后,子类就变成了可变类。考虑到多态的存在,这种“歧义”非常危险。详见:实现不可变类时如何禁止子类化?

例:String类、Interger类等:

1
public final class String

final方法

与final类的语义相似,final方法表示该方法是完整的,禁止重写。final标记会写入class文件的成员方法定义中,在编译期检查,如果发现继承链上游有函数签名相同的final方法(重写),则编译失败,从语法上保证不能重写一个final方法;同时,在编译期完成对final方法的解析(静态绑定)。

例:com.sun.org.apache.bcel.internal.classfile.Signature中的dump方法:

1
2
3
4
5
public final void dump(DataOutputStream file) throws IOException
{
super.dump(file);
file.writeShort(signature_index);
}

final变量

final成员变量

final成员变量体现了最直观的final语义:final成员变量表示该成员变量只读(在第一次赋值后就不能再修改)。final标记会写入class文件的成员变量定义中,在编译器和运行期都会进行检查,如果发现第二次修改就编译失败或抛出异常。

如果想实现不可变类,通常建议尽量使用final修饰每一个成员变量(延迟初始化等会违背这一建议)。

如果final成员变量的初始化被收集到该类的构造方法中,则final成员变量初始化之前的变量通常具有内存可见性,但这一性质并不容易应用,不建议利用。详见:一文解决内存屏障

例:Integer#value:

1
2
3
4
5
private final int value;
public Integer(int value) {
this.value = value;
}

显式的用构造方法初始化final成员变量。

常量

如果在成员变量声明时完成初始化(第一次赋值),则该成员变量被JVM视作常量。

对于编译期能确定初始值的常量(如final int a = 1;),通常能通过常量折叠常量传播等技术,在编译期完成常量的优化和解析。

例:Integer.SIZEInteger.BYTES

1
2
3
@Native public static final int SIZE = 32;
public static final int BYTES = SIZE / Byte.SIZE;

final局部变量

final局部变量也表示该局部变量只读,但与final成员变量不同,final局部变量只是java中的语法糖,局部变量上的final修饰并不会写入class文件,更无法出现在运行期。如果局部变量被final修饰,则编译器在编译时会检查该变量是否有可能发生第二次修改,有可能就编译失败;否则,消除final修饰。

例:

final局部变量

扫描微信关注我
微信公众号二维码
本文链接:Java中的final关键字
作者:猴子007
出处:https://monkeysayhi.github.io
本文基于 知识共享署名-相同方式共享 4.0 国际许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名及链接。
我是猴子007,<br>一只非常特殊的动物,<br>可以从事程序的开发、维护,<br>经常因寻找香蕉或母猿而无心工作。