如何理解 Final 关键字?

在 Java 中,final 关键字是一个修饰符,用于定义变量、方法或类的不可变性或不可继承性。

它的核心作用是限制修改或继承,从而增强代码的安全性、可读性和设计约束。

作用范围

修饰变量

局部变量:必须在声明时或使用前赋值一次,之后不可修改。

1
2
final int x = 10;
// x = 20; // 编译错误:无法修改 final 变量

成员变量:必须在声明时、构造方法或初始化块中赋值一次。

1
2
3
4
5
6
7
8
class MyClass {
    final int MAX_VALUE; // 非静态 final 变量
    final static int MIN_VALUE = 0; // 静态 final 常量

    public MyClass() {
        MAX_VALUE = 100; // 在构造方法中赋值
    }
}

引用类型变量

final 修饰的是引用,而非对象本身。引用不可变,但对象内部状态可能可变。

1
2
3
final List<String> list = new ArrayList<>();
list.add("Java"); // 允许修改对象内容
// list = new ArrayList<>(); // 编译错误:引用不可变

修饰方法

方法不可被重写:子类无法覆盖父类的 final 方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Parent {
    public final void print() {
        System.out.println("Parent");
    }
}

class Child extends Parent {
    // @Override // 编译错误:无法重写 final 方法
    // public void print() { ... }
}

设计意图:

  • 防止关键方法(如算法逻辑、安全校验)被子类意外修改。

修饰类

类不可被继承:

1
2
3
4
final class StringUtils { // 工具类通常设计为 final
    // 工具方法...
}
// class ExtendedUtils extends StringUtils { } // 编译错误

设计意图:

  • 防止继承破坏类的内部逻辑(如 StringInteger 等不可变类)。
  • 强制使用组合而非继承(如工具类)。

设计意义

安全性

  • 常量定义:通过 final 变量定义全局常量(如配置参数),防止意外修改。
  • 不可变对象:结合私有字段和 final 修饰符,实现线程安全的不可变对象。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public final class ImmutablePoint {
    private final int x;
    private final int y;

    public ImmutablePoint(int x, int y) {
        this.x = x;
        this.y = y;
    }
    // 只有 getter,没有 setter
}

性能优化

  • 编译优化:final 常量在编译时可能被直接替换为字面量(类似宏)。
  • JVM 优化:final 方法可能被内联(Inline)以提高执行效率。

设计约束

  • 强制规范:通过 final 限制扩展或修改,明确类的职责(如工具类)。
  • 防止破坏性继承:避免子类覆盖方法导致父类逻辑失效。

注意事项

引用类型变量

final 只能保证引用不变,但对象内部状态可能被修改(除非对象本身不可变)。

1
2
final int[] arr = {1, 2, 3};
arr[0] = 100; // 允许修改数组内容

final 与不可变对象

final 是实现不可变对象的必要条件,但非充分条件。需结合以下条件:

  • 所有字段用 final 修饰。
  • 字段为基本类型或不可变对象。
  • 不对外暴露修改内部状态的方法(如 setter)。

final 参数

方法参数可以声明为 final,防止在方法内被意外修改(增强可读性)。

1
2
3
public void process(final String input) {
    // input = "new value"; // 编译错误
}

强制约束机制

对变量的强制约束

编译阶段检查: 编译器会严格检查 final 变量的赋值次数。以下代码会直接编译失败:

1
2
3
final int x;
x = 10;
x = 20; // 编译错误:Variable 'x' might already have been assigned

运行时不可修改: 即使通过字节码操作(如直接修改 .class 文件),试图改变 final 变量的值也会导致 IllegalAccessError。

(注:常规开发中无法绕过此限制。)

对方法的强制约束

子类重写直接报错: 如果子类尝试重写父类的 final 方法,编译器会直接拒绝:

1
2
3
4
5
6
7
8
class Parent {
    public final void doSomething() {}
}

class Child extends Parent {
    @Override // 编译错误:Cannot override the final method
    public void doSomething() {}
}

对类的强制约束

禁止继承的编译检查: 任何尝试继承 final 类的行为都会导致编译错误

1
2
final class UtilityClass {}
class SubClass extends UtilityClass {} // 编译错误:Cannot inherit from final class

边界场景

反射修改 final 字段

理论上的可能性: 通过反射 API(如 Field.setAccessible(true)),可以修改 final 字段的值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class MyClass {
    final int value = 10;
}

public static void main(String[] args) throws Exception {
    MyClass obj = new MyClass();
    Field field = MyClass.class.getDeclaredField("value");
    field.setAccessible(true);
    field.setInt(obj, 20); // 修改 final 字段的值
    System.out.println(obj.value); // 输出 20(旧版本 Java 可能允许,新版默认禁止)
}

实际限制:

  • 从 Java 12 开始,默认禁止通过反射修改 final 字段,会抛出 IllegalAccessException。
  • 可通过 JVM 参数 –add-opens java.base/java.lang=ALL-UNNAMED 绕过,但这属于破坏性操作,违背语言设计原则。

引用类型变量的内部可变性

final 仅约束引用,不约束对象内容:

1
2
3
final List<String> list = new ArrayList<>();
list.add("Java"); // 合法操作:修改对象内部状态
// list = new ArrayList<>(); // 非法操作:修改引用

此时 final 强制的是引用不可变,但对象本身可能仍可变(除非对象自身设计为不可变,如 String)。

final 是强制的?

语言规范定义

Java 语言规范明确要求 final 的约束必须被遵守,否则代码无法通过编译(JLS §4.12.4)。

编译器和运行时的双重保障

  • 编译器:静态检查语法合法性。
  • JVM:运行时内存模型保证 final 字段的初始化安全(如 final 字段的写入对其他线程可见)。

设计哲学

final 的强制性是为了保障代码的可靠性和一致性。例如:

  • 不可变对象(String)依赖 final 的强制约束。
  • 工具类(如 Math)通过 final 类禁止继承,确保方法逻辑不被篡改。
想都是问题,做才是答案!
使用 Hugo 构建
主题 StackJimmy 设计