当前位置: 首页 > news >正文

Java:final的作用和原理介绍

一. final 的作用

class A {private final Object obj;                // #1public A(){this.obj  = new Object();            // $1}public A(Object obj){this.obj  = obj;                     // $2}public void doTest(){Object o1 = new Object();final Object o2 = o1;                // #2Runnable run = new Runnable() {public void run() {System.out.println(o2);}};o1 = new Object();Thread t = new Thread(run);t.start();}
}

1. 语义层面

       类中由final修饰的属性#1,方法中由final修饰的变量#2,表示:引用不可变(不能重新赋值)。如果尝试重新赋值,编译器会报错。

2. 多线程可见性

       final 的可见性保证仅限于构造函数内的赋值$1、$2(安全发布模式)。

       在普通代码块中#2,final 不提供内存屏障,因此:其他线程可能看到 o2 的旧值(尽管实际场景中 HotSpot JVM 可能会优化,但规范不保证)。

        如果 o2 的赋值在构造函数内,JVM 会保证可见性。

3. 匿名内部类访问外部变量

       在 Java 中,匿名内部类(如 Runnable)访问外部局部变量时,该变量必须是 final 或等效不可变(Java 8 后允许 effectively final)。

       代码中,如果移除 final:

  • 但 o2 后续不修改(effectively final),Java 8+ 仍允许编译。
  • 但如果 o2 被修改(如 o2 = new Object();),则必须用 final 修饰。

二. final 的内存语义

根据 JLS §17.5(Java 语言规范):

final 字段的写入必须在对象的构造函数完成之前完成,才能保证其他线程看到正确的初始值。

        final关键字的可见性保证,仅限于对象的构造阶段(即构造函数内的赋值),而不适用于普通赋值。

具体规则如下:

1. final 在构造函数内的赋值(安全发布)

如果一个 final 字段在 构造函数内 被赋值,JVM 会确保:

  • 该字段的写入不会被重排序到构造函数之外(防止其他线程看到未初始化的值)。
  • 当构造函数执行完毕时,final 字段的值对所有线程可见(类似于 volatile 的初始化安全)。

 java示例:

class MyClass {final Object o2;MyClass(Object o1) {this.o2 = o1;  // 构造函数内赋值,保证可见性}
}

安全发布的两种方式:

(1) 构造函数内赋值(显式初始化)
class A {final Object obj;A() {this.obj = new Object();  // 安全发布}
}
(2) 声明时直接赋值(隐式初始化)
class A {final Object obj = new Object();  // 是否安全发布?
}

        final 字段在声明时直接赋值,仍然属于安全发布,因为:编译器会将声明时的赋值逻辑放到构造函数中(字节码层面等同于构造函数内初始化)。

        上述代码编译后等价于:

class A {final Object obj;A() {this.obj = new Object();  // 编译器自动插入}
}

      JVM 会保证这种写法的可见性,其他线程读取 A.obj 时不会看到 null 或未初始化的状态。指令重排序被禁止:JVM 会确保 final 字段的初始化操作不会被重排序到构造函数之外。

2. final 在普通代码块的赋值(无内存屏障)

       如果 final 字段在 非构造阶段(如普通方法或代码块)赋值,它不会提供任何内存屏障或可见性保证。

Object o1 = new Object();
final Object o2 = o1;  // 普通赋值,无内存屏障

       这里的 final 仅表示 o2 引用不可变(不能重新赋值),但 不保证其他线程能立即看到 o2 的值。

Object o1 = new Object();
final Object o2 = o1;Runnable run = new Runnable() {public void run() {System.out.println(o2);}
};
o1 = new Object();Thread t = new Thread(run);
t.start();

       在这段代码中,final 关键字用于声明局部变量 o2 是不可变的。这意味着一旦 o2 被赋值后,就不能再更改它的引用。

      具体来说,在 Java 中,匿名内部类(如这里的 Runnable 实现)不能访问外部作用域中的非 final 局部变量,这是因为这些变量在方法执行结束后可能不再存在,而匿名内部类的对象可能会比方法活得更久。因此,Java 强制要求对外部局部变量的引用必须是 final 或 effectively final(即实际上不会被修改)。

      如果去掉 final 关键字,编译器会报错,因为 o2 在匿名内部类中被使用,但它不是 final 的。

相关文章:

  • Vue 3.5 :新特性全解析与开发实践指南
  • Python作业练习2
  • 解锁生命周期评价密码:OpenLCA、GREET 与 R 语言的融合应用
  • 浅析AI大模型为何需要向量数据库?从记忆存储到认知进化
  • 图灵爬虫练习平台 第十四题 逆向
  • 2025年金融创新、区块链与信息技术国际会议(FRCIT 2025 2025)
  • aardio - 虚表 —— 绘制整行背景进度条功能
  • RASP的运行时注入与更新
  • Pycharm的终端执行allure命令出现command not found
  • 通信算法之274 : SCFDE与OFDM技术对比分析‌
  • 高并发系统设计需要考虑哪些问题
  • DIFY教程第七弹:Echarts可视化助手生成图表
  • 【Axure视频教程】中继器表格间批量控制和传值
  • 榕壹云搭子系统技术解析:基于Spring Boot+MySQL+UniApp的同城社交平台开发实践
  • NumPy 2.x 完全指南【九】常量
  • git经验
  • 基于Qt的app开发第八天
  • 聊一聊Electron中Chromium多进程架构
  • 如何优化 Linux 服务器的磁盘 I/O 性能
  • 自动化测试基础知识详解
  • 国台办:民进党当局刻意刁难大陆配偶,这是不折不扣的政治迫害
  • 习近平同巴西总统卢拉会谈
  • 超新星|18岁冲击中超金靴,王钰栋的未来无限可能
  • 人民日报访巴西总统卢拉:“巴中关系正处于历史最好时期”
  • 对话郑永年:我们谈判也是为世界争公义
  • 张笑宇:物质极大丰富之后,我们该怎么办?