善用 final 关键字,提升编码内功的一大捷径
目录
final关键字的作用
修饰类
修饰方法
修饰成员变量
修饰局部变量
static final 常量的编译期优化
不可变对象在 GC 中的优化
final 域的重排序规则
题外话
如何扩展被 final 修饰的类?
在阅读本文章前,我想问在读的你一个问题。
为什么源码中喜欢用 final 关键字修饰变量呢?
下面截取自 JDK 8 中 HashMap 类的官方源码
如果你已经对该问题了如指掌,那么恭喜,你已经掌握了 final 关键字的奥秘 ~
final关键字的作用
修饰类
final 关键字修饰类代表该类不可被继承
修饰方法
final 关键字修饰方法代表该方法不能被重写
修饰成员变量
解决上述问题有三种方式
第一:构造器中初始化
class Person {final String name;public Person(String name) {this.name = name;}public void say() {System.out.println("I am " + this.name);}}
第二:显示赋值
class Person {final String name = "iron man";public void say() {System.out.println("I am " + this.name);}}
第三:代码块中初始化
class Person {final String name;{name = "iron man";}public void say() {System.out.println("I am " + this.name);}}
最后来看Java中对于final修饰的成员变量有哪些限制
对于 final 修饰的基本类型变量,在该属性被初始化后就不能被重新赋值了。
对于 final 修饰的引用类型变量,虽然在该属性被初始化后不能被重新赋值【这里不能重新赋值指的是不能指向另一个地址值】但能够修改里面的属性内容。
修饰局部变量
使用限制:使用前必须显示初始化,否则报错。
其它限制与成员变量一致,这里不再赘述。
static final 常量的编译期优化
static 搭配 final 修饰成员变量表示该变量为编译期常量
编译期常量必须显示初始化或者在静态代码块中初始化,否则编译器会爆红。
编译期优化演示:
FinalTest1中定义了一个DEFAULT_VALUE常量,FinalTest2中引用这个常量进行输出
class FinalTest1 {static final String DEFAULT_VALUE = "hello world";static {System.out.println("FinalTest1 被初始化了...");}}class FinalTest2 {public static void main(String[] args) {System.out.println(FinalTest1.DEFAULT_VALUE);}}
加了final关键字结果如下:
没有加 final 关键字结果如下:
从而我们可以得出结论,一个类中引用另一个类的常量并不会引发该类进行初始化。
这里补充一下类初始化时机相关知识
主动引用
在Java虚拟机规范中,对于类加载中的第一阶段“加载”并没有明确的规定,但是对于“初始化”阶段什么时候开始则做出了非常严格的规定,指出有且只有 6 种场景会触发初始化。
1、遇到new、getStatic、putStatic、invokeStatic这四条字节码指令时:使用new创建一个实例对象、读取或设置一个类型的静态字段【被final修饰除外】、调用一个类型的静态方法的时候。
2、使用反射调用方法时,如果类型没有被初始化,那么首先要进行初始化。3、当初始化类时,如果其父类没有被初始化,则会先触发其父类的初始化
4、当虚拟机启动时,用户首先需要制定一个main方法作为启动类,虚拟机会先初始化这个主类。
5、JDK7新加入的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial这四种类型的方法句柄,并且这个方法句柄没有被初始化时,则需先触发其初始化。
6、JDK8中,一个接口定义了默认方法(被default修饰的接口方法),如果有这个接口的实现类发生了初始化,那么该接口要先在该实现类初始化之前初始化。
这六种场景中的行为称为对一个类型进行主动引用。除此之外,所有引用类型的方式都不会触发初始化操作,称为被动引用。
被动引用
- 通过子类引用父类的静态字段,不会导致子类初始化。
- 通过数组定义来引用类,不会触发此类的初始化。
- 常量在编译阶段会存入调用类的常量池中,本质上没有直接应用到定义常量的类,因此不会触发定义常量的类的初始化。
不可变对象在 GC 中的优化
垃圾回收算法在扫描存活对象的时候会从GC Roots节点开始,扫描所有存活对象的引用来构建对象图。不可变对象在 GC 中的优化主要体现在老年代中,如果老年代对象引用了新生代对象,HotSpot 虚拟机中使用了一种称为卡表的结构来避免每次Young GC都扫描老年代中全部的对象引用。简单来说,当老年代中的对象发生对新生代中对象产生新的引用关系或者释放引用时,都会在卡表中相应的标记为脏(dirty),所以在发生Young GC时,扫描这些脏的项就可以了。而可变对象对其它对象的引用关系可能会频繁变化,并且有可能在运行过程中持有越来越多的引用,特别是容器。这些都会导致对应的卡表项被频繁的标记为脏项。不可变对象的引用关系非常稳定,在扫描卡表时就不会扫到它们对应的项了。
final 域的重排序规则
对于 final 域,编译器和处理器要遵守两个重排序规则:
在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能进行重排序。【会在写final域之后,构造函数返回之前插入一个StoreStore屏障】
初次读一个包含 final 域对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。【会在读final域之前插入一个LoadLoad屏障】
也就是说,如果对象构造函数内有 final 域,必须先用构造函数构造对象,再把对象赋值给其它引用。如果对象有final域,必须先读对象的引用,再读final域。
这两条规则可以确保在引用变量为任意线程可见之前,该引用变量指向的对象final域已经在构造函数中被正确初始化了。
拓展阅读:关键字: final详解 | Java 全栈知识体系
题外话
如何扩展被 final 修饰的类?
final class FinalClass {private String name;public void say() {System.out.println("Hello, I am " + this.name);}}
一种解决思路是提供一个包装类,将final类定义为包装类的成员属性
class FinalClassWrapper {private FinalClass finalClass;public FinalClassWrapper(FinalClass finalClass) {this.finalClass = finalClass;}public void wrapperSay() {System.out.println("Who are you ?");finalClass.say();}}
其实被修饰为 final 的类本就不该被扩展。
想起一句话:所有不能解决的问题,答案都在另外一个层次。
参考文章:
java基础之 GC
善用 final 关键字,提升编码内功的一大捷径相关推荐
- JAVA day07 权限,封装,JavaBean(规范代码),static、final关键字
1.权限 在Java中提供了四种访问权限,使⽤不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限: public:公共的 protected:受保护的 default(friendly):默认 ...
- Java学习总结:11(final关键字)
final关键字 在Java中final称为终结器,在Java中可以使用final定义类.方法和属性. 一.使用final定义的类不能再有子类,即:任何类都不能继承以final声明的父类. 在设计类的 ...
- final关键字最全了解
final关键字的使用: 在Java中声明类.属性和方法时,可使用关键字final来修饰. 1. final标记的类不能被继承: 2. final标记的方法不能被子类复写: 3. final标记的变量 ...
- 浅谈Java中的final关键字
浅析Java中的final关键字 谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来 ...
- 浅析Java中的final关键字
浅析Java中的final关键字 谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来 ...
- 在Java中使用final关键字可以提高性能吗?
本文翻译自:Does use of final keyword in Java improve the performance? In Java we see lots of places where ...
- Swift - final关键字的介绍,以及使用场景
final关键字在大多数的编程语言中都存在,表示不允许对其修饰的内容进行继承或者重新操作.Swift中,final关键字可以在class.func和var前修饰. 通常大家都认为使用final可以更好 ...
- 云漫圈 | 腾讯面试,我竟然输给了final关键字
戳蓝字"CSDN云计算"关注我们哦! 作者:乔戈里 来源:程序员乔戈里 腾讯面试现场 ------ final 在 Java 中是一个保留的关键字,可以声明变量.方法.类. 什么是 ...
- 面试:一文搞懂 final 关键字的作用
前言 Java语言支持的变量类型有: 类变量:独立于方法之外的变量,用 static 修饰. 实例变量:独立于方法之外的变量,不过没有 static 修饰. 局部变量:类的方法中的变量. 实例代码: ...
最新文章
- BP算法双向传_链式求导最缠绵(深度学习入门系列之八)
- 在Windows10上基于WSL2运行Linux端图形应用程序
- hive或mysql报错Too many connections
- php实现ftp上传,PHP_PHP实现ftp上传文件示例,FTP上传是PHP实现的一个常见且 - phpStudy...
- datetime.strptime格式转换报错ValueError
- Excel数据分析实例
- SPSS作业-如何判别是否服从正态分布
- 统一检测和分割任务!港科大清华IDEA提出基于Transformer统一目标检测与分割框架Mask DINO,效果SOTA!...
- spring源码构建时缺失spring-cglib-repack-3.2.4.jar和spring-objenesis-repack-2.4.jar
- Python进阶(十八)Python3爬虫小试牛刀
- 有了自动驾驶和共享无人车,未来出行将会是什么样的体验?
- 基于django的个人博客开发
- FPGA 24 工程模块 红外遥控(NEC协议)解码
- idea html有没有母版,多母版(三):建立子样
- python flask 微信小程序_python-flask微信小程序搭建
- traffic-filter
- Windows 将文件或文件夹拖动到bat批处理上强制删除
- 如何将文件重置或恢复到特定版本?
- vsan加入不同型号服务器,VMware VSAN的特点与要求,与优缺点
- 个人时尚竞聘简历PPT模板