news 2026/4/3 3:33:45

Java final关键字学习笔记:原来“不可变”这么有用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java final关键字学习笔记:原来“不可变”这么有用
    • 一、先搞懂final到底是啥
    • 二、final修饰类:不能被继承的“铁疙瘩”
    • 三、final修饰方法:不能被重写的“固定逻辑”
    • 四、final修饰变量:最常用也最容易踩坑
      • 4.1 基本类型变量:值真的不能改
      • 4.2 引用类型变量:引用不变,内容可变!
      • 4.3 成员变量和局部变量的区别
      • 4.4 final参数:方法里不能改引用
    • 五、并发场景下的final:线程安全的小帮手
    • 六、使用final的一些小经验
    • 最后想说

接触Java有一阵子了,final这个关键字一直没彻底搞明白,总觉得它就是“不能改”的意思,但实际用起来总踩坑。这几天花时间仔细研究了下,发现里面门道还真不少,整理成笔记方便自己回顾,也希望能帮到和我一样困惑的朋友。

一、先搞懂final到底是啥

我认为final最核心的作用就是“声明不可变”,就像给代码加了把锁,告诉自己也告诉别人,被它修饰的东西不能随便动了。但这里的“不能动”得分情况说,不是一刀切的不能改,具体要看修饰的是类、方法还是变量,这点特别关键,之前我就因为没分清踩过坑。

二、final修饰类:不能被继承的“铁疙瘩”

被final修饰的类,就相当于一个成品,不能再被扩展了,没有子类能继承它。我觉得这种设计特别适合那些功能完整、不需要再修改的类,比如Java自带的String类,要是能被继承改写,字符串的不可变性就没保障了。

给大家整个简单的例子,比如写个工具类,不想别人随便继承篡改:

// final修饰的工具类,不能被继承publicfinalclassCalculateUtil{// 私有构造器,不让外部实例化privateCalculateUtil(){}// 加法工具方法publicstaticintsum(inta,intb){returna+b;}}// 下面这行代码会编译报错,因为不能继承final类// class SubCalculateUtil extends CalculateUtil {}

在我看来,这种用法很适合工具类或者安全敏感的类,能守住核心逻辑不被破坏,避免有人瞎继承搞出问题。

三、final修饰方法:不能被重写的“固定逻辑”

final修饰方法的话,子类能继承这个方法直接用,但不能重写它的实现。这就像父类定好的规矩,子类必须遵守,不能擅自修改。

举个实际的例子感受下:

classAnimal{// 普通方法,子类可以重写publicvoideat(){System.out.println("动物吃东西");}// final方法,子类不能重写publicfinalvoidsleep(){System.out.println("动物睡觉(固定逻辑)");}}classDogextendsAnimal{// 重写普通方法,没问题@Overridepublicvoideat(){System.out.println("狗吃骨头");}// 下面这行代码会报错,不能重写final方法// @Override// public void sleep() {// System.out.println("狗趴着睡");// }}

还有个小知识点要提一下,private方法默认就是隐式final的,因为子类根本访问不到,自然没法重写。如果子类写了个和父类private方法同名的方法,那只是子类自己的新方法,不算重写。

四、final修饰变量:最常用也最容易踩坑

这是final最常用的场景,但也是最容易出错的地方。核心规则就一条:final变量一旦赋值,就不能再重新赋值了。但这里要区分基本类型和引用类型,差别很大。

4.1 基本类型变量:值真的不能改

如果final修饰的是int、double这些基本类型,那它的值就彻底固定了,改不了一点。

publicclassFinalBasicDemo{publicstaticvoidmain(String[]args){// 声明时直接赋值finalintnum=10;// 下面这行报错,不能重新赋值// num = 20;// 先声明后赋值,也只能赋一次finalStringname;name="小明";// 下面这行也报错// name = "小红";}}

这种用法很适合定义常量,比如项目里的配置参数,用final修饰能防止不小心被篡改。

4.2 引用类型变量:引用不变,内容可变!

这是我之前踩过的大坑!final修饰对象、集合这种引用类型时,只是说这个引用不能指向新的对象,但对象里面的内容该怎么改还能怎么改。

给大家整个直观的例子:

importjava.util.ArrayList;importjava.util.List;classStudent{privateStringname;publicStudent(Stringname){this.name=name;}publicvoidsetName(Stringname){this.name=name;}publicStringgetName(){returnname;}}publicclassFinalReferenceDemo{publicstaticvoidmain(String[]args){// final修饰Student对象finalStudentstudent=newStudent("张三");System.out.println("初始名字:"+student.getName());// 可以修改对象里面的内容,没问题student.setName("李四");System.out.println("修改后名字:"+student.getName());// 下面这行报错,不能让引用指向新对象// student = new Student("王五");// 集合的例子更明显finalList<String>list=newArrayList<>();// 可以往集合里加元素list.add("苹果");list.add("香蕉");System.out.println("集合内容:"+list);// 下面这行报错,不能指向新集合// list = new ArrayList<>();}}

所以如果想让对象彻底不可变,光用final修饰引用可不够,还得把对象里的字段也用final修饰,并且不提供修改方法,就像String类那样。

4.3 成员变量和局部变量的区别

成员变量(也就是类里定义的变量)用final修饰的话,必须显式初始化,要么声明时直接赋值,要么在构造器里赋值,而且所有构造器都得赋。

classPerson{// 声明时直接赋值privatefinalStringgender="男";// 构造器里赋值privatefinalintage;privatefinalStringaddress;// 第一个构造器publicPerson(intage,Stringaddress){this.age=age;this.address=address;}// 第二个构造器也得赋值publicPerson(intage){this.age=age;this.address="默认地址";}}

局部变量(方法里定义的变量)就灵活点,可以先声明后赋值,但必须在第一次使用前赋好值,而且只能赋一次。

4.4 final参数:方法里不能改引用

方法参数用final修饰的话,在方法里面就不能给这个参数重新赋值了,能防止不小心改了传入的引用。

publicclassFinalParamDemo{publicstaticvoidhandleData(finalintid,finalList<String>data){// 下面两行都报错,不能重新赋值// id = 100;// data = new ArrayList<>();// 可以修改集合内容,没问题data.add("处理后的"+id);}publicstaticvoidmain(String[]args){List<String>data=newArrayList<>();data.add("原始数据");handleData(1,data);System.out.println(data);}}

我觉得这种用法在处理复杂逻辑时很有用,能避免不小心改了参数引用导致的bug。

五、并发场景下的final:线程安全的小帮手

这部分有点深,但很实用。在我看来,final在多线程里最大的价值就是能保证可见性,而且不用额外加同步,零成本线程安全。

简单说就是,只要对象是正确构造的(没有发生this逸出),那么这个对象里的final字段,在其他线程里看到的一定是初始化后的最终值,不会是默认值。

给大家整个例子:

classSafeData{privatefinalintcode;privatefinalStringmessage;publicSafeData(intcode,Stringmessage){this.code=code;this.message=message;}publicintgetCode(){returncode;}publicStringgetMessage(){returnmessage;}}publicclassFinalThreadDemo{publicstaticvoidmain(String[]args)throwsInterruptedException{SafeDatadata=newSafeData(200,"成功");Threadthread=newThread(()->{// 子线程能正确读到final字段的值System.out.println("状态码:"+data.getCode());System.out.println("信息:"+data.getMessage());});thread.start();thread.join();}}

这里关键是不能让this逸出,也就是不能在构造器还没执行完的时候,就把this引用传给其他线程。比如在构造器里启动线程并传入this,那其他线程可能会读到还没初始化好的final字段,就出问题了。

还有个小对比,final和volatile都能保证可见性,但不一样:final只保证初始化后的一次可见性,适合不会修改的字段;volatile保证多次读写的可见性,适合经常修改的共享变量。而且两者都不保证原子性,比如i++这种操作,还是得用锁或者原子类。

六、使用final的一些小经验

我们的经验是,合理用final能让代码更健壮,但别过度使用,不然会降低灵活性。

总结几个常用场景:

  • 修饰类:工具类、安全敏感类,不想被继承的类;
  • 修饰方法:核心算法、不想被重写的逻辑;
  • 修饰变量:常量、不需要修改的配置、多线程共享的只读数据;
  • 修饰参数:防止方法内误改参数引用。

还有几个常见误区要避开:

  • 以为final修饰引用类型就是对象不可变,其实不是;
  • 过度使用final,比如每个变量都加,反而不方便;
  • 构造器里让this逸出,导致final的可见性失效。

最后想说

final关键字看着简单,其实里面的细节还挺多的。核心就是分清“不可变的是什么”——是类不能继承,方法不能重写,还是变量不能重新赋值。

我觉得掌握好final,不仅能减少bug,还能让代码的意图更清晰,别人一看就知道哪些东西是不能动的。现在我写代码的时候,遇到该固定的东西就会下意识用final修饰,感觉代码确实稳定了不少。

如果有理解不到位的地方,欢迎大家指正呀!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/28 4:34:37

LAMIC:一种无需学习、布局可控的多参考图像生成方法

概述 本文提出了一种新方法&#xff0c;即 LAMIC&#xff0c;用于在可控图像生成过程中使用多张参考图像进行带有布局信息的高质量合成。 传统的扩散模型在基于单个参考图像生成时具有优势&#xff0c;但在处理多个参考图像时&#xff0c;会出现 "不一致的身份退化 "…

作者头像 李华
网站建设 2026/3/19 10:43:50

KitchenOwl:跨平台智能购物清单的终极解决方案

KitchenOwl&#xff1a;跨平台智能购物清单的终极解决方案 【免费下载链接】kitchenowl KitchenOwl is a self-hosted grocery list and recipe manager. The backend is made with Flask and the frontend with Flutter. Easily add items to your shopping list before you g…

作者头像 李华
网站建设 2026/3/13 19:07:07

《Python 责任链模式实战指南:从设计思想到工程落地》

《Python 责任链模式实战指南&#xff1a;从设计思想到工程落地》 一、开篇引入&#xff1a;为什么要学习责任链模式&#xff1f; 在软件开发中&#xff0c;我们常常需要处理一系列请求&#xff1a;日志系统要根据不同级别输出信息&#xff1b;Web 框架要根据请求类型选择合适的…

作者头像 李华
网站建设 2026/4/2 2:09:23

永久免费的win系统垃圾清理软件 释放磁盘空间

软件简介→软件下载地址← Glary Disk Cleaner是一款永久免费的 Windows 磁盘清理工具&#xff0c;由 Glarysoft Ltd. 开发&#xff0c;专注于释放磁盘空间、提升系统性能和保护用户隐私。它通过快速扫描技术自动识别并清理系统垃圾文件&#xff0c;界面直观易用&#xff0c;无…

作者头像 李华
网站建设 2026/4/2 21:37:35

Webfunny性能监控系统架构设计与高效实施指南

Webfunny性能监控系统架构设计与高效实施指南 【免费下载链接】webfunny_monitor webfunny是一款轻量级的前端性能监控系统&#xff0c;也是一款埋点系统&#xff0c;私有化部署&#xff0c;简单易用。Webfunny is a lightweight front-end performance monitoring system and …

作者头像 李华
网站建设 2026/4/1 10:23:40

3大内存优化实战:让漏洞扫描性能提升80%的终极指南

3大内存优化实战&#xff1a;让漏洞扫描性能提升80%的终极指南 【免费下载链接】vuls Agent-less vulnerability scanner for Linux, FreeBSD, Container, WordPress, Programming language libraries, Network devices 项目地址: https://gitcode.com/gh_mirrors/vu/vuls …

作者头像 李华