final的8个小细节,听说只有高手才知道!你知道几个?

final关键字是一个常用的关键字,可以修饰变量、方法、类,用来表示它修饰的类、方法和变量不可改变,下面就聊一下使用 final 关键字的一些小细节。

细节一、final 修饰类成员变量和实例成员变量的赋值时机

对于类变量:

  1. 声明变量的时候直接赋初始值

  2. 在静态代码块中给类变量赋初始值

如下代码所示:

public class FinalTest {    //a变量直接赋值private final static  int a = 1;private final static  int b;//b变量通过静态代码块赋值static {b=2;}
}

对于实例变量:

  1. 在声明变量的时候直接赋值

  2. 在非静态代码块中赋值

  3. 在构造器中赋初始化值

如下代码所示:

public class FinalTest {//c变量在在声明时直接赋值private final  int c =1;private final  int d;private final  int e;//d变量在非静态代码块中赋值{d=2;}//e变量在构造器中赋值FinalTest(){e=3;}
}

细节二、当 final 修饰的成员变量未对它进行初始化时,会出现错误吗?

答:会出现错误。因为 java 语法规定,final 修饰的成员变量必须由程序员显示的初始化,系统不会对变量进行隐式的初始化。

如下图所示,未初始化变量就会出现编译错误:

细节三、final 修饰基本类型变量和引用类型变量的区别

如果 fianl 修饰的是一个基本数据类型的数据,一旦赋值后就不能再次更改。

那么 final 修饰的是引用数据类型呢?这个引用的变量能够改变吗?

看下面的代码:

public class FinalTest {//在声明final实例成员变量时进行赋值private final static Student student = new Student(50, "Java");public static void main(String[] args) {//对final引用数据类型student进行更改student.age = 100;System.out.println(student.toString());}static class Student {private int age;private String name;public Student(int age, String name) {this.age = age;this.name = name;}@Overridepublic String toString() {return "Student{" +"age=" + age +", name='" + name + '\'' +'}';}}
}//下面是打印结果
Student{age=100, name='Java'}

从打印结果可以看到:引用数据类型变量 student 的 age 属性修改成 100,是可以修改成功的。

结论:

  1. 当 final 修饰基本数据类型变量时,不能对基本数据类型变量重新赋值,因此基本数据类型变量不能被改变。

  2. 对于引用类型变量而言,它仅仅保存的是一个引用,final 只保证这个引用类型变量所引用的地址不会发生改变,即一直引用这个对象,但这个对象里面的属性是可以改变的。

细节四、final 修饰局部变量的场景

fianl 局部变量由程序员进行显示的初始化,如果 final 局部变量进行初始化之后就不能再次进行更改。

如果 final 变量未进行初始化,可以进行赋值,并且只能进行一次赋值,一旦赋值之后再次赋值就会出错。

下面的代码演示 final 修饰局部变量的情况:

细节五、final 修饰方法会对重载有影响吗?重写呢?

对于重载:final 修饰方法后是可以重载的

如下代码:

public class FinalTest {public final void test(){}//重载方法不会出现问题public final void test(String test){}
}

对于重写:当父类的方法被 final 修饰的时候,子类不能重写父类的该方法

如上代码所示,可以看到会出现 cannot override ,overridden method is final 的编译错误提示

细节六、final 修饰类的场景

当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用 final 进行修饰。

final 类中的成员变量可以根据需要设为 final,但是要注意 final 类中的所有成员方法都会被隐式地指定为 final 方法。

细节七、写 final 域的重排序规则,你知道吗?

这个规则是指禁止对 final 域的写重排序到构造函数之外,这个规则的实现主要包含了两个方面:

  1. JMM 禁止编译器把 final 域的写重排序 到 构造函数 之外

  2. 编译器会在 final 域写之后,构造函数 return 之前,插入一个 StoreStore 屏障。这个屏障可以禁止处理器把 final 域的写重排序到构造函数之外

给举个例子,要不太抽象了,先看一段代码

public class FinalTest{private int a;  //普通域private final int b; //final域private static FinalTest finalTest;public FinalTest() {a = 1; // 1. 写普通域b = 2; // 2. 写final域}public static void writer() {finalTest = new FinalTest();}public static void reader() {FinalTest demo = finalTest; // 3.读对象引用int a = demo.a;    //4.读普通域int b = demo.b;    //5.读final域}
}

假设线程 A 在执行 writer()方法,线程 B 执行 reader()方法。

由于变量 a 和变量 b 之间没有依赖性,所以就有可能会出现下图所示的重排序

由于普通变量 a 可能会被重排序到构造函数之外,所以线程 B 就有可能读到的是普通变量 a 初始化之前的值(零值),这样就可能出现错误。

而 final 域变量 b,根据重排序规则,会禁止 final 修饰的变量 b 重排序到构造函数之外,从而 b 能够正确赋值,线程 B 就能够读到 final 域变量 b初始化后的值。

结论:写 final 域的重排序规则可以确保在对象引用为任意线程可见之前,对象的 final 域已经被正确初始化过了,而普通域就不具有这个保障。

细节八:读 final 域的重排序规则,你知道吗?

这个规则是指在一个线程中,初次读对象引用和初次读该对象包含的 final 域,JMM 会禁止这两个操作的重排序。

还是上面那段代码

public class FinalTest{private int a;  //普通域private final int b; //final域private static FinalTest finalTest;public FinalTest() {a = 1; // 1. 写普通域b = 2; // 2. 写final域}public static void writer() {finalTest = new FinalTest();}public static void reader() {FinalTest demo = finalTest; // 3.读对象引用int a = demo.a;    //4.读普通域int b = demo.b;    //5.读final域}
}

假设线程 A 在执行 writer()方法,线程 B 执行 reader()方法。

线程 B 可能就会出现下图所示的重排序

可以看到,由于读对象的普通域被重排序到了读对象引用的前面,就会出现线程 B 还未读到对象引用就在读取该对象的普通域变量,这显然是错误的操作。而 final 域的读操作就“限定”了在读 final 域变量前已经读到了该对象的引用,从而就可以避免这种情况。

结论:读 final 域的重排序规则可以确保在读一个对象的 final 域之前,一定会先读包含这个 final 域的对象的引用。

结束

今天给大家总结了一下使用 final 关键字容易忽视的一些小细节,看完希望你能有所收获。


往期推荐

Java中的Switch都支持String了,为什么不支持long?


对象复制的7种方法,还是Spring的最好用!


分布式ID生成的9种方法,特好用!


关注我,每天陪你进步一点点!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/545010.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java实现的简单程序登录界面

2019独角兽企业重金招聘Python工程师标准>>> 这是我写的简单代码: 简单,没什么嚼头,作业贴,直接上代码。文件保存用户名和密码,输入密码错误3次退出程序。 [java] view plaincopy 01.public Login() throws…

try-catch-finally中的4个巨坑,老程序员也搞不定!

作者 | 王磊来源 | Java中文社群(ID:javacn666)转载请联系授权(微信ID:GG_Stone)在 Java 语言中 try-catch-finally 看似简单,一副人畜无害的样子,但想要真正的“掌控”它&#xff0…

CentOS7安装Python3.4 ,让Python2和3共存

为什么80%的码农都做不了架构师?>>> #CentOS7安装Python3.4 ,让Python2和3共存 环境:CentOS7.1 需求:网络畅通 编译需要的一些包,酌情安装 yum groupinstall "Development tools" yum install z…

字节二面:优化 HTTPS 的手段,你知道几个?

由裸数据传输的 HTTP 协议转成加密数据传输的 HTTPS 协议,给应用数据套了个「保护伞」,提高安全性的同时也带来了性能消耗。因为 HTTPS 相比 HTTP 协议多一个 TLS 协议握手过程,目的是为了通过非对称加密握手协商或者交换出对称加密密钥&…

求首位相连一维数组最大子数组的和

结对成员: 朱少辉:主要负责代码编写 侯涛亮:主要负责程序测试 题目:一个首尾相接的一维整型数组,其中有正有负,求它的最大子数组并返回它的位置。 思路:在求一维子数组的基础上,先输入一个含有N…

SpringBoot接口幂等性实现的4种方案!

作者 | 超级小豆丁来源 | www.mydlq.club/article/94目录什么是幂等性什么是接口幂等性为什么需要实现幂等性引入幂等性后对系统的影响Restful API 接口的幂等性如何实现幂等性方案一:数据库唯一主键方案二:数据库乐观锁方案三:防重 Token 令…

Redis为什么变慢了?一文详解Redis性能问题 | 万字长文

Redis 作为优秀的内存数据库,其拥有非常高的性能,单个实例的 OPS 能够达到 10W 左右。但也正因此如此,当我们在使用 Redis 时,如果发现操作延迟变大的情况,就会与我们的预期不符。你也许或多或少地,也遇到过…

蜕变成蝶~Linux设备驱动之字符设备驱动

一、linux系统将设备分为3类:字符设备、块设备、网络设备。使用驱动程序: 字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据。字符设备是面向流的设备&#x…

感动哭了!《Java 编程思想》最新中文版开源!

前言还记得这本书吗?是不是已经在你的桌上铺满厚厚的一层灰了?随着 Java 8 的出现,这门语言在许多地方发生了翻天覆地的变化。最新版已经出来了,在新的版本中,代码的运用和实现上与以往不尽相同。本书可作为编程入门书…

韩信大招:一致性哈希

作者 | 悟空聊架构来源 | 悟空聊架构韩信点兵的成语来源淮安民间传说。常与多多益善搭配。寓意越多越好。我们来看下主公刘邦和韩信大将军的对话。刘邦:“你觉得我可以带兵多少?”韩信:“最多十万。”刘邦不解的问:“那你呢&#…

mysql连接非常慢的觖决办法及其它常见问题解决办法

2019独角兽企业重金招聘Python工程师标准>>> 编辑/etc/mysql/my.cnf 在[mysqld]段中加入 skip-name-resolve 重启mysql 禁用DNS反响解析,就能大大加快MySQL连接的速度。 转载于:https://my.oschina.net/ydsakyclguozi/blog/401768

最常见的10种Java异常问题!

封面:洛小汐译者:潘潘前言本文总结了有关Java异常的十大常见问题。目录检查型异常(checked) vs. 非检查型异常(Unchecked)异常管理的最佳实践箴言为什么在try代码块中声明的变量不能在catch或者finally中被…

Java获取文件类型的5种方法

前言工作中经常会用到,判断一个文件的文件类型,这里总结一把,一般判断文件类型的原理有2种方式:根据文件扩展名判断优点:速度快,代码简单缺点:无法判断出真实的文件类型,例如一些伪造…

你以为用了BigDecimal后,计算结果就一定精确了?

BigDecimal,相信对于很多人来说都不陌生,很多人都知道他的用法,这是一种java.math包中提供的一种可以用来进行精确运算的类型。很多人都知道,在进行金额表示、金额计算等场景,不能使用double、float等类型,…

Google 开源的依赖注入库,比 Spring 更小更快!

来源 | zhuanlan.zhihu.com/p/24924391Guice是Google开源的一个依赖注入类库,相比于Spring IoC来说更小更快。Elasticsearch大量使用了Guice,本文简单的介绍下Guice的基本概念和使用方式。学习目标概述:了解Guice是什么,有什么特点…

这个 bug 让我更加理解 Spring 单例了

谁还没在 Spring 里栽过跟头呢,从哪儿跌倒,就从哪儿睡一会儿,然后再爬起来。讲点儿武德 这是由一个真实的 bug 引起的,bug 产生的原因就是忽略了 Spring Bean 的单例模式。来,先看一段简单的代码。public class TestS…

mysql优化--叶金荣老师讲座笔记

copy to tmp table执行ALTER TABLE修改表结构时建议:凌晨执行Copying to tmp table拷贝数据到内存中的临时表,常见于GROUP BY操作时建议:创建索引Copying to tmp table on disk临时结果集太大,内存中放不下,需要将内存…

废弃fastjson!大型项目迁移Gson保姆级实战

前言本篇文章是我这一个多月来帮助组内废弃fastjson框架的总结,我们将大部分Java仓库从fastjson迁移至了Gson。这么做的主要的原因是公司受够了fastjson频繁的安全漏洞问题,每一次出现漏洞都要推一次全公司的fastjson强制版本升级,很令公司头…

学到了!MySQL 8 新增的「隐藏索引」真不错

MySQL 8.0 虽然发布很久了,但可能大家都停留在 5.7.x,甚至更老,其实 MySQL 8.0 新增了许多重磅新特性,比如栈长今天要介绍的 "隐藏索引" 或者 "不可见索引"。隐藏索引是什么鬼? 隐藏索引 字面意思…

iOS开发-自定义UIAlterView(iOS 7)

App中不可能少了弹框,弹框是交互的必要形式,使用起来也非常简单,不过最近需要自定义一个弹框,虽然iOS本身的弹框已经能满足大部分的需求,但是不可避免还是需要做一些自定义的工作。iOS7之前是可以自定义AlterView的&am…