Final详解
一、基础篇:final 的三种用法(像给变量上锁)
1. final 变量(一次性密码锁)
// 声明时赋值(像设置初始密码)
final int MAX_RETRY = 3;
// 构造函数赋值(像首次使用必须修改密码)
class User {
final String id;
public User(String id) {
this.id = id; // 这里必须赋值!
}
}
2. final 方法(禁止子类修改)
class Parent {
// 像贴上"禁止覆盖"的标签
final void importantMethod() {
// 关键逻辑...
}
}
class Child extends Parent {
// 这里会编译报错!无法修改父类的重要方法
// void importantMethod() { ... }
}
3. final 类(终极防护罩)
// 像用防弹玻璃保护的类
final class SecurityUtils {
// 这个类不能被继承
}
// 编译错误!无法继承final类
// class HackerUtils extends SecurityUtils { ... }
二、进阶篇:final 在并发中的魔法
1. 构造函数的"安全结界"(可视化原理)
class SafeBox {
final String secret;
public SafeBox(String password) {
this.secret = password; // ① 写final域
// 编译器在此插入"结界"(内存屏障)
} // ② 对象引用赋值
}
2. 多线程下的安全读取
// 主线程
final SharedData data = new SharedData(); // 正确构造的final对象
// 线程A
new Thread(() -> {
System.out.println(data.value); // 保证看到正确值
}).start();
3. 典型错误案例:提前暴露的保险箱
class LeakExample {
final int secret;
static LeakExample leaked;
public LeakExample() {
secret = 123; // 设置密码
leaked = this; // 危险!保险箱还没锁好就暴露位置
// 其他初始化操作...
}
}
三、高手进阶:JVM 如何实现 final
1. 类文件中的 final 标记
class Demo {
final int number = 42;
}
对应的 class 文件结构:
字段表:
access_flags: ACC_FINAL (0x0010)
name: "number"
descriptor: "I"
2. 内存屏障的真相(快递打包比喻)
- StoreStore 屏障:像快递员打包时先放重要物品(final 变量),再贴快递单(对象引用)
- LoadLoad 屏障:像收件人必须先检查快递单(对象引用),再拆封重要物品(final 变量)
四、实际应用场景
1. Spring 中的 final 依赖注入
@Service
public class OrderService {
private final PaymentService paymentService;
// 构造函数注入(线程安全)
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}
2. 多线程配置类
public class AppConfig {
// 全局配置项(安全发布)
public static final ExecutorService executor =
Executors.newFixedThreadPool(4);
}
五、常见问题解答
Q:为什么匿名类访问外部变量要加 final?
void demo() {
int count = 0;
// 需要改为:final int count = 0;
new Thread(() -> {
System.out.println(count); // 编译错误!
}).start();
}
原因:匿名类可能在其他线程执行,final 保证值的稳定性
Q:反射能修改 final 字段吗?
Field field = MyClass.class.getDeclaredField("FINAL_FIELD");
field.setAccessible(true);
field.set(obj, newValue); // 抛出IllegalAccessException!
解决方法:通过反射修改 Field 的 modifiers 字段(危险操作!)
六、面试专题:final 高频 10 问(P7 级考点)
Q1:为什么 lambda 表达式/匿名内部类访问外部变量必须是 final?
void demo() {
int count = 0;
// 实际编译时会自动加final
Runnable r = () -> System.out.println(count);
}
答案:
- 生命周期不一致:外部方法栈帧销毁后仍需访问变量
- 值一致性:保证所有线程看到相同值
- JVM 实现:自动生成合成字段(synthetic field)
Q2:final 字段在类加载哪个阶段初始化?
答案:
类加载流程:
加载 → 验证 → 准备(默认值)→ 解析 → 初始化(执行<clinit>)
↓
final static基本类型:准备阶段直接赋值
Q3:如何实现线程安全的延迟初始化?
Spring 最佳实践:
@Service
public class LazyService {
private final Object lock = new Object();
private volatile Resource resource;
public Resource getResource() {
if (resource == null) {
synchronized (lock) {
if (resource == null) {
resource = new Resource();
}
}
}
return resource;
}
}
Q4:final 字段与序列化的冲突如何解决?
public class User implements Serializable {
private final String uid;
// 自定义序列化逻辑
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
ois.defaultReadObject();
// 通过反射重新初始化final字段
Field uidField = User.class.getDeclaredField("uid");
uidField.setAccessible(true);
uidField.set(this, (String) ois.readObject());
}
}
Q5:final 引用逃逸问题如何检测?
案例分析:
public class EscapeExample {
final int x;
static EscapeExample instance;
public EscapeExample() {
x = 42;
instance = this; // 危险!
}
}
检测工具:
- FindBugs:RC_REF_COMPARISON_BAD
- IDEA Inspection:“this” escape in constructor
Q6:final vs volatile 对比
| 特性 | final | volatile |
|---|---|---|
| 可见性 | 构造函数完全初始化后可见 | 写操作后立即可见 |
| 原子性 | 只保证引用可见性 | 不保证复合操作原子性 |
| 使用场景 | 不可变对象 | 状态标志 |
Q7:反射修改 final 字段的黑魔法
Field field = User.class.getDeclaredField("uid");
field.setAccessible(true);
// 移除final修饰符
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(user, "new_id"); // 危险操作!
⚠️ 导致问题:
- 破坏编译器优化
- 可能引发 SecurityManager 告警
- 多线程环境下出现可见性问题
Q8:为什么内部类持有外部类引用必须是 final?
JVM 层解释:
// 源代码
class Outer {
void foo() {
final int local = 42;
new Inner() {
void bar() { System.out.println(local); }
};
}
}
// 编译后生成:
class Outer$1 extends Inner {
private final int val$local;
Outer$1(int local) { this.val$local = local; }
void bar() { System.out.println(val$local); }
}
Q9:String 类的 final 设计哲学
public final class String {
private final char value[];
// 所有修改操作都返回新对象
public String concat(String str) { /*...*/ }
}
设计优势:
- 安全性:防止篡改(如网络请求参数)
- 性能:缓存 hashCode、线程安全
- 内存:字符串常量池优化
Q10:final 方法继承的边界情况
class Parent {
public final void method() {}
}
class Child extends Parent {
// 编译错误:Cannot override
// public void method() {}
// 合法:隐藏父类方法(非重写)
public static void method() {}
}
JLS 规范:
- final 方法不能被子类重写
- 允许静态方法隐藏父类实例方法(不推荐)
Read other posts