final 是 Java 中的一个重要关键字,主要用于表示“不可变”或“最终的”,它可以在类、方法和变量三个层面上使用。本文将详细探讨 final 的作用,并补充一些面试中可能会遇到的问题。
final 关键字的作用当 final 修饰一个类时,表示这个类不能被继承,是类继承体系中的最终形态。例如:
final class MyFinalClass { }
// class SubClass extends MyFinalClass { } // 编译错误!String、Integer 和 LocalDateTime 等不可变类。String 要设计成 final 的?因为 String 是不可变的(immutable),这是其安全性的基石。如果 String 可被继承,就可能被恶意子类修改其行为(比如重写 equals() 或 hashCode()),导致缓存哈希值失效、字符串池混乱及安全漏洞(如 URL、密码被篡改)等。
用 final 修饰的方法不能在子类中被重写。例如:
class Parent {
final void doNotOverride() { }
}
class Child extends Parent {
// void doNotOverride() { } // 编译错误!
}Object.getClass()、Thread.start())。final 方法进行内联优化(inlining),减少虚方法调用开销。final 方法会影响性能吗?会!final 方法是编译时绑定(静态绑定),JVM 知道它不会被重写,可以直接内联,提升执行效率。而非 final 的虚方法需要动态绑定,有查虚函数表的开销。
分为三种情况:
类型 | 行为 |
|---|---|
基本类型 | 值不能变(常量) |
引用类型 | 引用地址不能变,但对象内容可变 |
成员变量 | 必须在构造器或声明时初始化 |
局部变量 | 可以延迟赋值,但只能赋一次 |
例如:
final int x = 10;
// x = 20; // ❌ 编译错误
final StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // ✅ 允许:对象内容可变
// sb = new StringBuilder(); // ❌ 不允许:引用不能变final 局部变量和普通变量有什么区别?final 局部变量可以在声明时不初始化(称为“blank final”),但必须在使用前赋值,且只能赋一次。final 或 effectively final(实际上不变)的局部变量。这是为了保证内外数据一致性。组合 | 是否允许 | 原因 |
|---|---|---|
final + abstract 类 | ❌ 不允许 | abstract 要求被继承,final 禁止继承,矛盾 |
final + abstract 方法 | ❌ 不允许 | abstract 要求重写,final 禁止重写,矛盾 |
final + static | ✅ 允许 | 常用于定义常量:public static final int MAX = 100; |
final + private 方法 | ✅ 允许 | 但 private 方法本就不能被重写,加 final 意义不大(但合法) |
final 与线程安全final 变量在多线程环境下具有特殊的语义: final 字段的初始化是线程安全的:JVM 保证一旦对象构造完成,final 字段的值对所有线程可见(无需额外同步)。例如,String 的 value 数组是 final 的,确保一旦创建,内容不会被修改,多个线程可安全共享。
final 的最佳实践final(尤其是工具类、不可变类)finalpublic static finalfinal,提高可读性和安全性(现代 IDE 支持自动提示 effectively final)final 或 effectively final 的变量final 不只是一个语法关键字,更是一种设计思想——通过限制可变性来提升代码的安全性、可维护性和性能。它是构建不可变对象、线程安全类和高性能系统的重要基石。