深入浅出的讲解Java关键字final的作用

 更新时间:2023年06月06日 11:54:37   作者:alexhilton  
final是Java中非常常见的一个关键字,可以说每天都在使用它,虽然常见,但却也不见得都那么显而易见,今天就来研究一下final,以加深对它的理解和更合理的运用,需要的朋友可以参考下

final可以用来干什么

修饰类

当一个类不想被继承时,就可以用final来修饰。

修饰方法

当一个方法不想被子类覆写(Override)时,可以用final来修饰。另外一方面,把方法用final来修饰也有一定的性能提升上的帮助,因为虚拟机知道它不会被覆写,所以可以以更简单的方式来处理。

private的方法,默认都会被编译器加上final.

修饰变量

被final修饰的变量只能赋值一次,之后不能再被修改。如:

final int a = 10;
a = 4; // compilation error

需要注意的是,这里说的是只能赋值一次,并不意味着,非要在声明变量时直接初始化,比如,下面的代码也是完全合法的:

final int a;
if (foo()) {
    a = 3;
} else {
    a = 4;
}

修饰域变量

域变量也是变量,所以用final来修饰的第一个作用就是赋值后,不能再修改变量的值,比如:

final int a = 10;
final Object b = new Object();

对于基本类型来说,就是变量值不能再被修改;对于引用来说,就是不能再让其指向其他对象或者null。

但对于域变量,声明为final的域变量必须在声明时初始化,或者在构造方法中初始化,否则会有编译错误。

此外,声明为final的域变量还有内存模型上的语义,下面详细说

内存模型的作用--防止变量从构造方法中逸出

这个主要是针对被final修饰的域变量,虚拟机会有禁止指令重排的保证:

  • 在构造方法内对一个final变量的写入,与随后这个被构造对象的引用赋值给一个引用变量,这二个顺序不改变,final变量的写入一定要早于对象引用的赋值。

什么意思呢?在多线程环境下,域变量是有可能从构造方法中逸出的,也就是说线程有可能读到还没有被构造方法初始化的域变量的值。比如:

class Foo {
    int a;
    Foo(int v) {
        a = v;
    }
}

如果是在多线程环境下,一个线程A在创建Foo的对象,另一个线程B在读对象的a的值,则B是有可能读到未正确初始化a的值(默认初始值0)。这就是域变量从构造方法中逸出。

关键字final可以禁止虚拟机指令重排,从而保证了构造方法执行完毕前final修饰的变量一定是初始化过了的。

匿名内部类使用外部变量时为何要强制使用final修饰

这个大家肯定都习以为常了,比如

private void initViews() {
    final int a = 3; // Compilation error if remove final
    btn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            if (a > 1) {
                // volala
            }
        }
    }
}

那么,有没有想过为什么?而像其他支持完整闭包的语言如JavaScript,Python等,是没有这等限制的。究其原因,是Java对闭包支持不够完整,或者说它并不是像动态语言那样的完整闭包。对于匿名内部类来说,编译器会创建一个命名类(OutClass$1之类的),然后把匿名类所在的能捕获的变量,以构造参数的形式传递给内部类使用,这样一样,外部的变量与内部类看到的变量是不同的,虽然它们的值是相同的,因此,如果再允许外部修改这些变量,或者内部类里面修改这些变量,都会造成数据的不一致性(因为它们是不同的变量),所以Java强制要求匿名内部类访问的外部变量要加上final来修饰。

对于其他语言,匿名内部类,持有的是外部变量的一个包装的引用(wrapper reference),这可以能看不懂,但是理解起来就是内部类能直接访问外部变量,外部与闭包内部访问的是同一个变量,因此外部修改了,内部能看到变化,内部修改了,外部也能看到变化。

一句话总结就是,Java内部类与外部持有的是值相同的不同的变量;其他支持闭包的语言则持有的是相同的变量。

建议能使用final的地方就加上final修饰

最后来聊聊,啥时候应该用final呢?孤的建议(以及众多大师的建议)就是能多用就多用,除非不能用final,否则就用。原因,有这么几条:

  • 域变量尽可能加上final

    这个原因比较明确,前面也提到了,在多线程条件下,会有很大的优势。尽可能加上final来修饰域变量,甚至用Immutable Object,可以省去构造时的多线程同步。

    多线程最大的麻烦是状态同步,啥是状态?其实就是共享数据,域变量就是共享数据,所以,如果共享数据都是不可变的(Immutable),那么自然就没有了同步上的麻烦。

  • final类和方法能提升性能

    正常的类和方法,虚拟机需要为了继承和方法覆写而做一次准备,如果加上了final,虚拟机知道它不会被继承或者覆写,那么就可以做一些优化。虽然,这并不显著,但是还是可以提升一些性能的。

  • final变量能提升可读性

    无论是域变量还是本地变量,加上了final修饰,程序的维护者就知道了,这个变量的值不会再改变,这无疑会大大增加可读性。

以上就是深入浅出的讲解Java关键字final的作用的详细内容,更多关于Java 关键字 final的资料请关注脚本之家其它相关文章!

相关文章

  • springboot使用Mybatis-plus分页插件的案例详解

    springboot使用Mybatis-plus分页插件的案例详解

    这篇文章主要介绍了springboot使用Mybatis-plus分页插件的相关知识,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-05-05
  • Java线程创建静态代理模式代码实例

    Java线程创建静态代理模式代码实例

    这篇文章主要介绍了Java线程创建静态代理模式代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-11-11
  • 详解JavaWeb如何实现文件上传和下载功能

    详解JavaWeb如何实现文件上传和下载功能

    这篇文章主要介绍了如何利用JavaWeb实现文件的上传和下载功能,文中的示例代码讲解详细,对我们的学习或工作有一定的帮助,感兴趣的小伙伴可以学习一下
    2021-12-12
  • Spring使用RestTemplate模拟form提交示例

    Spring使用RestTemplate模拟form提交示例

    本篇文章主要介绍了Spring使用RestTemplate模拟form提交示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-03-03
  • Java 继承与多态的深入理解

    Java 继承与多态的深入理解

    这篇文章主要介绍了Java 继承与多态的深入理解的相关资料,子类继承父类的特征和行为,使得子类具有父类的各种属性和方法。或子类从父类继承方法,使得子类具有父类相同的行为,需要的朋友可以参考下
    2017-08-08
  • Seata分布式事务出现ABA问题解决

    Seata分布式事务出现ABA问题解决

    这篇文章主要为大家介绍了Seata分布式事务出现ABA问题解决方法示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • Java数据结构之二叉搜索树详解

    Java数据结构之二叉搜索树详解

    二叉搜索树作为一个经典的数据结构,具有链表的快速插入与删除的特点,同时查询效率也很优秀,所以应用十分广泛。本文将详细讲讲二叉搜索树的原理与实现,需要的可以参考一下
    2022-06-06
  • 如何更快乐的使用Java 8中的Lambda特性

    如何更快乐的使用Java 8中的Lambda特性

    从java8出现以来lambda是最重要的特性之一,它可以让我们用简洁流畅的代码完成一个功能。下面这篇文章主要给大家介绍了关于如何更快乐的使用Java 8中的Lambda特性的相关资料,需要的朋友可以参考下
    2018-11-11
  • 使用springboot通过spi机制加载mysql驱动的过程

    使用springboot通过spi机制加载mysql驱动的过程

    这篇文章主要介绍了使用springboot通过spi机制加载mysql驱动的过程,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • idea2020导入spring5.1的源码详细教程

    idea2020导入spring5.1的源码详细教程

    这篇文章主要介绍了idea2020导入spring5.1的源码的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-06-06

最新评论