news 2026/4/17 14:19:17

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/4/18 6:30:50

Fluent UI主题定制终极指南:5分钟掌握品牌视觉重塑技巧

Fluent UI主题定制终极指南&#xff1a;5分钟掌握品牌视觉重塑技巧 【免费下载链接】fluentui 项目地址: https://gitcode.com/GitHub_Trending/of/fluentui 想要让你的企业应用在视觉上脱颖而出&#xff0c;同时保持开发效率&#xff1f;Fluent UI主题定制功能正是你需…

作者头像 李华
网站建设 2026/4/18 6:28:38

PostfixAdmin 完整指南:5步搭建专业的邮件服务器管理系统

PostfixAdmin 完整指南&#xff1a;5步搭建专业的邮件服务器管理系统 【免费下载链接】postfixadmin PostfixAdmin - web based virtual user administration interface for Postfix mail servers 项目地址: https://gitcode.com/gh_mirrors/po/postfixadmin PostfixAdm…

作者头像 李华
网站建设 2026/4/9 20:28:15

3分钟掌握React Native键盘控制的终极指南

3分钟掌握React Native键盘控制的终极指南 【免费下载链接】react-native-keyboard-controller Keyboard manager which works in identical way on both iOS and Android 项目地址: https://gitcode.com/gh_mirrors/re/react-native-keyboard-controller React Native …

作者头像 李华
网站建设 2026/4/13 18:01:38

FaceFusion与Hugging Face集成:一键拉取最新模型版本

FaceFusion与Hugging Face集成&#xff1a;一键拉取最新模型版本 在生成式AI迅猛发展的今天&#xff0c;视觉内容的自动化处理早已不再是实验室里的概念——从短视频平台上的实时换脸特效&#xff0c;到影视工业中的数字替身合成&#xff0c;人脸替换技术正以前所未有的速度走向…

作者头像 李华
网站建设 2026/4/16 9:45:42

Science | 本周最新文献速递

文章标题&#xff1a;Mechanosensitive genomic enhancers potentiate the cellular response to matrix stiffness 中文标题&#xff1a; 揭秘细胞如何感知硬度&#xff01;发现“机械增强子”调控基因表达与细胞行为的新机制 关键词&#xff1a; 机械转导、基因组增强子、细胞…

作者头像 李华
网站建设 2026/4/16 12:05:34

告别Vim搜索混乱:Unite.vim让你的编辑器效率翻倍

告别Vim搜索混乱&#xff1a;Unite.vim让你的编辑器效率翻倍 【免费下载链接】unite.vim :dragon: Unite and create user interfaces 项目地址: https://gitcode.com/gh_mirrors/un/unite.vim Unite.vim是一款革命性的Vim插件&#xff0c;它通过统一搜索界面彻底改变了…

作者头像 李华