引子:一说到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的好处小结

  1. final关键字提高了性能。JVM和Java应用都会缓存final变量。
  2. final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销。
  3. 使用final关键字,JVM会对方法、变量及类进行优化。
  4. 对于不可变类,它的对象是只读的,可以在多线程环境下安全的共享,不用额外的同步开销。

四、来自《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关键字以及一些建议的更多相关文章

  1. 面试题系列:工作5年,第一次这么清醒的理解final关键字?

    面试题:用过final关键字吗?它有什么作用 面试考察点 考察目的: 了解面试者对Java基础知识的理解 考察人群: 工作1-5年,工作年限越高,对于基础知识理解的深度就越高. 背景知识 final关 ...

  2. 深入理解final关键字

    在了解了final关键字的基本用法之后,这一节我们来看一下final关键字容易混淆的地方. 1.类的final变量和普通变量有什么区别? 当用final作用于类的成员变量时,成员变量(注意是类的成员变 ...

  3. java基础4:深入理解final关键字

    本文主要介绍了final关键字的使用方法及原理 具体代码在我的GitHub中可以找到 https://github.com/h2pl/MyTech 文章首发于我的个人博客: https://h2pl. ...

  4. Java面试题,深入理解final关键字

    final关键字 final的简介 final可以修饰变量,方法和类,用于表示所修饰的内容一旦赋值之后就不会再被改变,比如String类就是一个final类型的类. final的具体使用场景 fina ...

  5. 浅析Java中的final关键字(转载)

    自http://www.cnblogs.com/dolphin0520/p/3736238.html转载 一.final关键字的基本用法 在Java中,final关键字可以用来修饰类.方法和变量(包括 ...

  6. 浅析Java中的final关键字

    浅析Java中的final关键字 谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来 ...

  7. 浅析final 关键字

    谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来了解final这个关键字的用法.下 ...

  8. [转载]浅析Java中的final关键字

    浅析Java中的final关键字 谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来 ...

  9. 转载:浅析Java中的final关键字

    谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来了解final这个关键字的用法.下 ...

随机推荐

  1. 通过渲染改变tabBarItem的背景图片

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px Menlo; color: #703daa } p.p2 { margin: 0.0px 0. ...

  2. JDBC详解系列(三)之建立连接(DriverManager.getConnection)

      在JDBC详解系列(一)之流程中,我将数据库的连接分解成了六个步骤. JDBC流程: 第一步:加载Driver类,注册数据库驱动: 第二步:通过DriverManager,使用url,用户名和密码 ...

  3. 【转载】漫谈HADOOP HDFS BALANCER

    Hadoop的HDFS集群非常容易出现机器与机器之间磁盘利用率不平衡的情况,比如集群中添加新的数据节点.当HDFS出现不平衡状况的时候,将引发很多问题,比如MR程序无法很好地利用本地计算的优势,机器之 ...

  4. 使用xUnit为.net core程序进行单元测试(中)

    第一部分: http://www.cnblogs.com/cgzl/p/8283610.html 下面有一点点内容是重叠的.... String Assert 测试string是否相等: [Fact] ...

  5. web前端教程:CSS 布局十八般武艺都在这里了

      CSS布局 布局是CSS中一个重要部分,本文总结了CSS布局中的常用技巧,包括常用的水平居中.垂直居中方法,以及单列布局.多列布局的多种实现方式(包括传统的盒模型布局和比较新的flex布局实现), ...

  6. typeof关键字的作用

    http://blog.chinaunix.net/uid-28458801-id-4200573.html 一.typeof详解: 前言:    typeof关键字是C语言中的一个新扩展,这个特性在 ...

  7. HDU 1000 A + B Problem(指针版)

    A + B Problem Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) To ...

  8. Uva 10339 - Watching Watches【数论,暴力】

    题目链接:10339 - Watching Watches 题意:两个时钟,一个每天慢a秒,一个每天慢b秒,问两钟重新相遇的时刻 1圈有12 * 60 * 60秒,然后1圈 / abs(a - b), ...

  9. POJ2689-Prime Distance-区间筛素数

    最近改自己的错误代码改到要上天,心累. 这是迄今为止写的最心累的博客. Prime Distance Time Limit: 1000MS   Memory Limit: 65536K Total S ...

  10. SSH私用私钥登陆

    1.导入私钥,将私钥文件放到当前登陆用户目录下的.ssh目录下 2.指定私钥登陆,ssh -i .ssh/ssh_rsa  root@target.com 如果出现了下面这种情况 这是因为私钥文件权限 ...