news 2025/12/24 7:43:16

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进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/19 9:59:47

命令行esh模板引擎实战技巧与最佳实践

esh (Embedded SHell) 是一个轻量级的模板引擎&#xff0c;用于在任意模板中嵌入和执行 shell 命令。本文档系统性地介绍 esh 的核心概念、语法特性、高级技巧和实战应用&#xff0c;帮助开发者快速掌握配置文件动态生成和模板化处理的精髓。 &#x1f4cb; 目录 一、快速开始…

作者头像 李华
网站建设 2025/12/19 9:57:37

HTMLProofer:一站式HTML质量验证终极解决方案

HTMLProofer&#xff1a;一站式HTML质量验证终极解决方案 【免费下载链接】html-proofer Test your rendered HTML files to make sure theyre accurate. 项目地址: https://gitcode.com/gh_mirrors/ht/html-proofer 在当今数字化时代&#xff0c;网站质量直接关系到用户…

作者头像 李华
网站建设 2025/12/19 9:57:04

SpringBoot集成Swagger:API文档自动生成的完整指南

SpringBoot集成Swagger&#xff1a;API文档自动生成的完整指南 【免费下载链接】springboot-guide SpringBoot2.0从入门到实战&#xff01; 项目地址: https://gitcode.com/gh_mirrors/sp/springboot-guide 在现代Web开发中&#xff0c;前后端分离架构已经成为主流趋势。…

作者头像 李华
网站建设 2025/12/19 9:55:51

【SpringBoot】Spring IOC DI 五大注解 Bean 扫描路径 依赖注入

文章目录Ⅰ. 什么是 IOC 和 DI❓❓❓Ⅱ. 五大注解Ⅲ. 注解 BeanⅣ. 扫描路径 ComponentScanⅤ. 依赖注入一、三种注入方式 Autowired① 属性注入② 构造方法注入③ Setter方法注入三种注入方式的区别二、Autowired 存在的问题① Primary② Qualifier③ Resource⭐⭐⭐Ⅰ. 什么是…

作者头像 李华
网站建设 2025/12/23 10:38:24

一句话生成专业问卷?百考通AI平台让调研“零门槛、高效率”!

你是否曾因为不会设计问卷而放弃一个好选题&#xff1f;是否在写论文或做项目时&#xff0c;明明有清晰的研究问题&#xff0c;却卡在“怎么把它变成一道道科学的问题”&#xff1f;又或者&#xff0c;花了一整天做的问卷被导师或同事指出“逻辑混乱”“选项不全”“问题有引导…

作者头像 李华
网站建设 2025/12/19 9:54:40

3步征服iOS WebApp状态栏:打造完美沉浸式全屏体验

3步征服iOS WebApp状态栏&#xff1a;打造完美沉浸式全屏体验 【免费下载链接】Mars 腾讯移动 Web 前端知识库 项目地址: https://gitcode.com/gh_mirrors/mar/Mars 还在为iOS WebApp状态栏遮挡内容而苦恼吗&#xff1f;想要让你的Web应用拥有原生App一样的全屏视觉效果…

作者头像 李华