深入理解final关键字以及一些建议
引子:一说到final关键字,相信大家都会立刻想起一些基本的作用,那么我们先稍微用寥寥数行来回顾一下。
一、final关键字的含义
final是Java中的一个保留关键字,它可以标记在成员变量、方法、类以及本地变量上。一旦我们将某个对象声明为了final的,那么我们将不能再改变这个对象的引用了。如果我们尝试将被修饰为final的对象重新赋值,编译器就会报错。
二、用法
1.修饰变量
final修饰在成员变量或者局部变量上,那么我们可以称这个变量是final变量,这可能使我们用到最多的地方,举个栗子:常量(虽然现在建议使用枚举类来代替常量)。
如果我们将被final修饰的变量重新赋值,编译器就会报出如图:cannot assign a value to final variable.(不能给final变量赋值)
虽然我们不能改变对象的引用,但是我们仍旧可以set对象的属性。
例如:
我们有一个People类:
- class People {
- private String name;
- private Integer age;
- // getter and setter
- }
我们来测试一下:
- public class Test {
- final People people = new People();
- people.setAge(20);
- people.setName("Mike"); // 不报错,可以给对象里面设值
- //people = new People(); // 改变引用将会报错,同上一样
- }
以上结论适用于集合类。
2.修饰方法
被final所修饰的方法将无法被子类重写。
“使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。” -- 摘自《Java编程思想》
因此如果你认为一个方法的功能已经足够完整了,子类中不需要改变的话,你可以声明此方法为final。final方法比非final方法要快,因为在编译的时候已经静态绑定了,不需要在运行时再动态绑定(正如编程思想中所提到的,在现在几版较新的JDK中,已经几乎没有性能差别了)。
(当我们尝试重写的时候编译器就会报错)。
注:类的private方法会隐式地被指定为final方法。
3.修饰类
如果某个类被final所修饰,那么表明这个的功能通常是完整的;该类将不能被继承。并且final类的所有方法都会被隐式的修饰成final。
4.ps:匿名类中的所有变量都必须是final的。
三、关键字final的好处小结
- final关键字提高了性能。JVM和Java应用都会缓存final变量。
- final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销。
- 使用final关键字,JVM会对方法、变量及类进行优化。
- 对于不可变类,它的对象是只读的,可以在多线程环境下安全的共享,不用额外的同步开销。
四、来自《Effective Java》中的一些建议
该书的第17条:要么为了继承而设计,并提供文档说明,要么就禁止继承。
该条目提醒我们,如果类不是被设计用来继承的,那么这个类就应该被禁止继承(听起来有点绕,但细想下来的设计思想是很好的),否则就应该提供足够的文档及注释(具体可参考java.util.AbstractCollection这个骨架实现里的注释文档规范)。
而禁止类被子类化的方法通常有两个:
1.将所有的构造器设为私有的(private)或者包级私有的(default),并使用静态工厂方法来代替构造器;
2.将类标记为final。
五、思考
1.一些思考回头再来审视我们日常中的程序,我们可能已经习惯了不去那么刻意的使用final,顶多在写常量的时候用一用,但实际上我们很多的类,方法或者变量是不需要被改变的,或者说不会被继承的。比如我在刚读到《Effective Java》中的这个条目后,回首自己正在做的一个项目中审视了一下,我首先将自己的domain层中的一些类标为了final,因为我觉得这些类是不可能被继承的,如果继承了是不太符合设计的,并且程序运行没有异常,同时修改的还有我的依赖注入方式(参考我的上一篇博客:Spring注解依赖注入的三种方式的优缺点以及优先选择)
我重新纠正了一下自己在设计类的时候的思想顺序:之前自己在准备写一个类的时候(虽然通常我是不给类加final的= =),可能觉得这个类(变量或者方法)不能被改变,有很强烈的这种想法时才会加上final,但现在是:这个类需不需要使他可以被子类化?如果在以后的项目更新,迭代中,并不需要,那么我会毫不犹豫的给他加上final。
2."final关键字能提升性能"?
当时发现这一点之后,我可能是中毒了,给能加上final的地方都加上了,自以为改善了性能心里还美滋滋呢。其实对这个“提升性能”一点一直还有一丝的疑问,于是我回头就去了Stack Overflow上转了一圈,找到了我想要的答案:Does use of final keyword in Java improve the performance?
大佬指出,通常是不会的,对于方法,HotPot会跟踪看它是否真的被重写了,并且能够优化没有被重写的内联方法,直到它加载到了一个类复写了这个方法,这时它可以撤销(或部分撤销)这些优化。(当然,这是假设您使用的是HotPot,但到目前为止这是最常见的JVM,所以…)
之后大佬指出了我们不应该为了这么丝许的性能而绞尽脑汁,建议我们应该明确设计,写出好的结构的代码以及可读性优良的代码。(在此又应证了《Effective Java》中的第55条:谨慎地进行优化中所指出的核心:优化的格言就是:不要进行优化) (也验证了上面《Java编程思想》中最后的那句话)
(ps: 原谅我翻译一般,英语好的可以点去原文看~)
3.关于局部变量以及参数中的final
接着我尝试将我的局部变量以及方法中的参数都标记为final的,同2一样,已经中毒颇深了。但是我对此同时也存在着同样的疑问,然后在Stack Overflow中得到了经验证的又一个结论:局部变量以及参数中的final,同样不能提升我们的性能,它甚至不会被写进字节码中。于是我操起了键盘啪啪啪一顿敲了几行代码编译了一下,并用反编译工具(如JD-GUI)打开:
先来看我们的源码:
- public class FinalTest {
- private static void say(final int number) {
- System.out.println("number: " + number);
- }
- public static void main(String[] args) {
- final int num = 0;
- say(num);
- }
- }
再来看看编译后的.class文件:
- public class FinalTest {
- public FinalTest() {
- }
- private static void say(int number) {
- System.out.println("number: " + number);
- }
- public static void main(String[] args) {
- int num = false;
- say(0);
- }
- }
可以看到在写入字节码的时候就被优化掉了,final只是编译时静态限制我们不能再赋值(改变引用)。
---------------------------- 2019年1月16日 更新 ----------------------------
【阿里Java编码规约】
18.【推荐】final可提高程序响应效率,声明成final的情况:
(1)不需要重新赋值的变量,包括类属性、局部变量;
(2)对象参数前加final,表示不允许修改引用的指向;
(3)类方法确定不允许被重写。
- 所有JVM都相关:方法参数与局部变量用final修饰是纯编译时信息,到Class文件里就已经没有踪迹了,JVM根本不会知道方法参数或者局部变量有没有被final修饰。这个是完全不可能影响性能的。
- HotSpot JVM相关:
- static final常量是好的,HotSpot VM里的JIT编译器会利用这个信息来做优化;
- 实例变量(字段)用final修饰的话,目前(直到最新的JDK8u)的HotSpot VM都不会使用这个信息来优化。只有JDK自身的一些核心类被区别对待(例如说java.lang.String上的final成员)。所以这个也不会影响性能。
- 实例方法用final修饰:其实在可以使用final修饰的场景下,就算没有用final修饰,HotSpot VM的JIT编译器也一样会通过<b>类层次分析</b>(Class Hierarchy Analysis,CHA)来发现这个方法是实质上只有一个实现版本的,所以最终生成的代码质量会一样,性能不会有区别。而在不能使用final修饰的场景下(例如说这个方法真的有多个实现),如果当前加载的类中其实只有单一实现(别的实现在尚未加载或已经卸载的类中),则HotSpot VM仍然可以通过CHA把它当作单一实现来做激进优化,并通过deoptimization来保证安全。所以在HotSpot VM的环境中,其实实例方法没有必要为了性能而加final,而是真的希望表达语义上的限制(例如java.lang.getClass()是一个不允许覆写的方法,加final修饰就很合理)时才应该使用。
所以JVM发展到现在,我们声明 final 的目的已经不再主要是性能问题了,而是正确性、合理性、严谨性的问题。用来提醒自己以及其他人,这里的参数/变量是真的不能被修改,并让Java编译器去检查到底有没有被乱改
深入理解final关键字以及一些建议的更多相关文章
- 面试题系列:工作5年,第一次这么清醒的理解final关键字?
面试题:用过final关键字吗?它有什么作用 面试考察点 考察目的: 了解面试者对Java基础知识的理解 考察人群: 工作1-5年,工作年限越高,对于基础知识理解的深度就越高. 背景知识 final关 ...
- 深入理解final关键字
在了解了final关键字的基本用法之后,这一节我们来看一下final关键字容易混淆的地方. 1.类的final变量和普通变量有什么区别? 当用final作用于类的成员变量时,成员变量(注意是类的成员变 ...
- java基础4:深入理解final关键字
本文主要介绍了final关键字的使用方法及原理 具体代码在我的GitHub中可以找到 https://github.com/h2pl/MyTech 文章首发于我的个人博客: https://h2pl. ...
- Java面试题,深入理解final关键字
final关键字 final的简介 final可以修饰变量,方法和类,用于表示所修饰的内容一旦赋值之后就不会再被改变,比如String类就是一个final类型的类. final的具体使用场景 fina ...
- 浅析Java中的final关键字(转载)
自http://www.cnblogs.com/dolphin0520/p/3736238.html转载 一.final关键字的基本用法 在Java中,final关键字可以用来修饰类.方法和变量(包括 ...
- 浅析Java中的final关键字
浅析Java中的final关键字 谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来 ...
- 浅析final 关键字
谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来了解final这个关键字的用法.下 ...
- [转载]浅析Java中的final关键字
浅析Java中的final关键字 谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来 ...
- 转载:浅析Java中的final关键字
谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来了解final这个关键字的用法.下 ...
随机推荐
- 一步步实现滑动验证码,Java图片处理关键代码
最近滑动验证码在很多网站逐步流行起来,一方面对用户体验来说,比较新颖,操作简单,另一方面相对图形验证码来说,安全性并没有很大的降低.当然到目前为止,没有绝对的安全验证,只是不断增加攻击者的绕过成本. ...
- windows环境VS2015编译TensorFlow C++程序完全攻略
本文参考和综合了多篇网络博客文章,加以自己的实践,最终终于在windows环境下,编译出可以用于C++程序调用tensorflow API的程序,并执行成功. 考虑到网络上关于这方面的资料还较少,特总 ...
- 第一篇:Win10系统搭建Python+Django+Nginx+MySQL 开发环境详解(完美版)
Win10+Python+Django+Nginx+MySQL 开发环境搭建详解 PaulTsao 说明:本文由作者原创,仅供内部参考学习与交流,转载引用请注明出处,用于商业目的请联系作者本人. Wi ...
- CentOS修改系统时间
CentOS修改系统时间 操作: 1. date –s '1987-05-02 10:10:10' 2. clock –w //将日期写入CMOS 补充: 修改Linux时间一般涉及到3个命令: 1. ...
- ASP.NET Core Web API下事件驱动型架构的实现(二):事件处理器中对象生命周期的管理
在上文中,我介绍了事件驱动型架构的一种简单的实现,并演示了一个完整的事件派发.订阅和处理的流程.这种实现太简单了,百十行代码就展示了一个基本工作原理.然而,要将这样的解决方案运用到实际生产环境,还有很 ...
- 小程序wxss和css3的区别
经过设置发现,微信小程序中wxss并不能完全支持css3的全部功能. 总结记录在此文中,以免忘记. 0 . wxss不能直接通过css3中的background-image属性来设置显示的背景图片. ...
- 浅谈JavaScript的面向对象程序设计(四)
本文继续讲解JavaScript的面向对象程序设计.继承是面向对象语言中的一个基本概念,面向对象语言支持两种继承实现方式:接口继承和实现继承.接口继承只继承方法签名,而实现继承则继承实际的方法.但是在 ...
- windows PAE扩展和AWE编程
在32位windows上只能看到最大3GB的内存空间,而且每个应用程序只能访问4GB的的内存,这个限制是windows独有的,为了使程序能够访问大于4GB的内存空间,需要使用AWE编程接口,同时需要开 ...
- [转载]CentOS 7安装Gnome GUI 图形界面
原文链接:http://www.centoscn.com/image-text/config/2015/0528/5552.html 当你安装centos服务器版本的时候,系统默认是不会安装 Cent ...
- web服务器,应用程序服务器,http服务器的区别
WEB服务器.应用程序服务器.HTTP服务器有何区别?IIS.Apache.Tomcat.Weblogic.WebSphere都各属于哪种服务器? 这个概念很重要. Web服务器的基本功能就是提供We ...