Java final关键字用法详解(附带实例)
在 Java 语言中,final 是一个保留的关键字,它可以用于修饰类、方法和变量,标记为“最终”的意思,这意味着它们一旦赋值或定义后就不可改变。
final 关键字有不同的用法,下面分别介绍 final 关键字用于修饰类、方法和变量时的用法。
例如下面使用 final 修饰类的示例代码:
final 修饰类的用法是通过在类的字节码中添加 ACC_FINAL 标志实现的,这使得虚拟机在加载类的时候知道这个类是不可继承的。
通过 javap 命令获取上述代码的字节码,如下图所示:
在上述字节码部分,我们看到 FinalClassTest 类的 flags 中添加了 ACC_FINAL 标志。
例如下面使用final修饰方法的示例代码:
通过 javap 命令获取上述代码的字节码,如下图所示:
在上述字节码部分,我们看到有 final 修饰的 myMethod1() 方法的 flags 中添加了 ACC_FINAL 标志;没有 final 修饰的 myMethod2() 方法的 flags 中没有添加 ACC_FINAL 标志。
例如下面使用 final 修饰变量的示例代码:
通过 javap 命令获取上述代码的字节码,如下图所示:
在上述字节码部分,我们看到在 final 修饰的 a 和 b 两个变量定义的 flags 中都添加了 ACC_FINAL 标志,表示这两个变量的值不能被改变。不过我们发现在 a 变量的定义中,使用了 ConstantValue 属性,而 b 变量的定义却没使用。
在 Java 字节码中,ConstantValue 是一个属性,它表示字段的常量值。这个属性只在字段使用 final 修饰,同时具有一个初始值时才存在。这个初始值必须是编译时常量,也就是说,这个值在编译时就已经确定,并且可以被直接嵌入任何使用该常量的代码中。
具体来讲,ConstantValue 属性用于基本数据类型(如int、long、float、double 等)和 String 类型的字段。如果没有在声明时直接赋予 final 字段一个编译时常量,那么它的字节码中不会有 ConstantValue 属性。
例如,上述示例中的 b 变量是一个数组引用型变量,虽然该变量被 final 修饰,但是它的字段值不是一个编译时常量,需要在运行时计算得出,因此不会有 ConstantValue 属性。
总体来说,final 关键字的底层原理是在编译器和虚拟机层面进行处理,确保被修饰的类、方法和变量的特定行为。这些特定行为有助于确保代码的稳定性和安全性。但请注意,final 并不是用来优化代码性能的关键字,而是用来约束程序行为的。
在一般情况下,final 变量的写操作会要求编译器在写操作之后插入一个 StoreStore 屏障,这可以确保写操作对其他线程的可见性。而 final 变量的读操作会要求编译器在读操作之前插入一个 LoadLoad 屏障,以确保读操作不会被乱序执行。通过插入内存屏障,可以保障 final 的语义和特性。
但是,对于 final 变量的内存屏障插入在不同平台和硬件上的行为可能会有所不同,具体插入与否还取决于编译器和虚拟机的策略。这也说明了编写并发代码时,不仅要依赖 final 的特性,还需要考虑使用其他同步手段来确保线程安全性。
final 关键字有不同的用法,下面分别介绍 final 关键字用于修饰类、方法和变量时的用法。
Java final修饰类
当一个类使用 final 修饰时,该类不能被继承,即不能有子类。例如下面使用 final 修饰类的示例代码:
public final class FinalClassTest { int a = 20; }
final 修饰类的用法是通过在类的字节码中添加 ACC_FINAL 标志实现的,这使得虚拟机在加载类的时候知道这个类是不可继承的。
通过 javap 命令获取上述代码的字节码,如下图所示:

在上述字节码部分,我们看到 FinalClassTest 类的 flags 中添加了 ACC_FINAL 标志。
Java final修饰方法
当一个方法使用 final 修饰时,该方法不能被子类重写。例如下面使用final修饰方法的示例代码:
public class FinalMethodTest { public final void myMethod1(){ System.out.println("final method!"); } public void myMethod2(){ System.out.println("ordinary method!"); } }final 修饰方法是通过在方法的字节码中添加 ACC_FINAL 标志来实现的,这使得虚拟机在运行时知道这个方法是不可重写的。
通过 javap 命令获取上述代码的字节码,如下图所示:

在上述字节码部分,我们看到有 final 修饰的 myMethod1() 方法的 flags 中添加了 ACC_FINAL 标志;没有 final 修饰的 myMethod2() 方法的 flags 中没有添加 ACC_FINAL 标志。
Java final修饰变量
当一个变量使用 final 修饰时,在该变量的字节码中也会添加 ACC_FINAL 标志,表示该变量的值不能被改变。对于基本类型(如 int、char 等),虚拟机在编译时直接将变量的值嵌入字节码中,因此这些值是不可修改的。对于引用类型(如对象引用),final 仅表示引用不可变,即引用不能指向其他对象,但对象本身的状态仍然是可变的。例如下面使用 final 修饰变量的示例代码:
public class FinalTest { final int a = 20; final int[] b = {1,3,5}; }
通过 javap 命令获取上述代码的字节码,如下图所示:

在上述字节码部分,我们看到在 final 修饰的 a 和 b 两个变量定义的 flags 中都添加了 ACC_FINAL 标志,表示这两个变量的值不能被改变。不过我们发现在 a 变量的定义中,使用了 ConstantValue 属性,而 b 变量的定义却没使用。
在 Java 字节码中,ConstantValue 是一个属性,它表示字段的常量值。这个属性只在字段使用 final 修饰,同时具有一个初始值时才存在。这个初始值必须是编译时常量,也就是说,这个值在编译时就已经确定,并且可以被直接嵌入任何使用该常量的代码中。
具体来讲,ConstantValue 属性用于基本数据类型(如int、long、float、double 等)和 String 类型的字段。如果没有在声明时直接赋予 final 字段一个编译时常量,那么它的字节码中不会有 ConstantValue 属性。
例如,上述示例中的 b 变量是一个数组引用型变量,虽然该变量被 final 修饰,但是它的字段值不是一个编译时常量,需要在运行时计算得出,因此不会有 ConstantValue 属性。
总体来说,final 关键字的底层原理是在编译器和虚拟机层面进行处理,确保被修饰的类、方法和变量的特定行为。这些特定行为有助于确保代码的稳定性和安全性。但请注意,final 并不是用来优化代码性能的关键字,而是用来约束程序行为的。
在一般情况下,final 变量的写操作会要求编译器在写操作之后插入一个 StoreStore 屏障,这可以确保写操作对其他线程的可见性。而 final 变量的读操作会要求编译器在读操作之前插入一个 LoadLoad 屏障,以确保读操作不会被乱序执行。通过插入内存屏障,可以保障 final 的语义和特性。
但是,对于 final 变量的内存屏障插入在不同平台和硬件上的行为可能会有所不同,具体插入与否还取决于编译器和虚拟机的策略。这也说明了编写并发代码时,不仅要依赖 final 的特性,还需要考虑使用其他同步手段来确保线程安全性。