final 关键字详解:特性与内存语义

4 阅读3分钟

final 关键字详解:特性与内存语义


一、基本特性

final 关键字用于声明不可变的实体,可修饰变量、方法和类,具体规则如下:


1. 修饰变量
  • 基本类型:值不可变。

  • 引用类型:引用不可变(不能指向其他对象),但对象内部状态可能可变。

    final List<String> list = new ArrayList<>();
    list.add("item"); // 允许修改对象内容
    // list = new ArrayList<>(); // 编译错误:引用不可变
    
2. 修饰方法
  • 不可重写:子类不能覆盖父类的 final 方法。

    class Parent {
        public final void method() {}
    }
    class Child extends Parent {
        // public void method() {} // 编译错误
    }
    
3. 修饰类
  • 不可继承:类不能被其他类继承。

    final class FinalClass {}
    // class SubClass extends FinalClass {} // 编译错误
    

二、初始化规则

1. 实例变量
  • 必须在声明时或构造函数中初始化

    class Example {
        final int a = 1; // 声明时初始化
        final int b;
        Example(int b) { this.b = b; } // 构造函数中初始化
    }
    
2. 静态变量(常量)
  • 必须在声明时或静态初始化块中初始化

    class Constants {
        static final int MAX_VALUE = 100; // 声明时初始化
        static final double PI;
        static { PI = 3.14; } // 静态初始化块中初始化
    }
    

三、内存语义

1. 可见性保证
  • 构造函数中初始化安全:正确构造的对象,其 final 域的值对其他线程可见,无需同步。

    class SafePublication {
        private final int value;
        public SafePublication(int value) {
            this.value = value; // 初始化完成后,value 对其他线程可见
        }
    }
    
2. 禁止重排序
  • 写操作屏障:JVM 在 final 域写入后插入 StoreStore 屏障,确保构造函数返回前完成写入。
  • 读操作屏障:读取 final 域时,JVM 插入 LoadLoad 屏障,确保获取最新值。
3. 安全发布
  • 条件:对象引用未在构造函数中逸出(this 未泄露)。
  • 效果:即使无同步,其他线程也能看到 final 域的正确初始值。

四、并发编程中的使用

1. 安全发布对象
  • 无逸出构造:确保 this 不在构造函数中被其他线程访问。

    public class SafeFinalExample {
        private final int x;
        public SafeFinalExample(int x) {
            this.x = x; // 正确初始化,无逸出
        }
    }
    
2. 不可变对象
  • 所有域均为 final:对象状态完全不可变,天然线程安全。

    public final class ImmutablePoint {
        private final int x;
        private final int y;
        public ImmutablePoint(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }
    
3. 注意事项
  • 引用类型内部状态final 不保证引用对象内部状态的线程安全,需额外同步。

    final List<String> list = Collections.synchronizedList(new ArrayList<>());
    

五、与 static 的结合

1. 静态常量
  • 编译时常量:若 static final 基本类型/字符串在编译时可确定值,会内联优化。

    static final int MAX = 100; // 编译时常量
    
2. 延迟初始化
  • 静态初始化块:复杂初始化逻辑。

    static final Map<String, Integer> MAP;
    static {
        MAP = new HashMap<>();
        MAP.put("key", 1);
    }
    

六、对比其他关键字

特性finalvolatilesynchronized
作用对象变量、方法、类变量代码块、方法
可见性初始化安全(构造函数内)强制主内存读写锁释放时刷新主内存
原子性不提供(引用对象需同步)不提供提供
有序性禁止构造函数内重排序禁止重排序(内存屏障)禁止重排序
性能开销高(锁竞争)

七、总结

  • 核心作用:通过不可变性简化并发编程,保障初始化安全。

  • 适用场景

    • 定义常量(基本类型或不可变对象)。
    • 构建线程安全的不可变类。
    • 防止方法或类被修改(增强封装性)。
  • 注意事项

    • final 引用对象的内部状态仍需同步。
    • 避免构造函数中 this 逸出,破坏初始化安全。

正确使用 final 能显著提升代码健壮性和可维护性,尤其在多线程环境中,结合 JMM 的内存语义,可有效减少同步开销。