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…

js对浏览器内部及外部窗口宽度及高度使用测试

<!DOCTYPE html> <html><head><meta charset"utf-8" /></head><body><p id"demo"></p><script>//Part I 取浏览器内部窗口宽度及高度//该例显示浏览器窗口的高度和宽度&#xff1a;&#xff08;不包…

golang rsa密钥_如何在Golang的地图中检查密钥是否存在?

golang rsa密钥When you try to get the value of key in a map, you get two return values. The first value is the value of key and second is bool value which could be either true or false. If a key is present in the map then the second value will be true else…

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

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

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

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

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

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

js使用location的方法实验

location.hostname 返回 web 主机的域名 127.0.0.1 location.port 返回 web 主机的端口 &#xff08;80 或 443&#xff09; 8020 location.protocol 返回所使用的 web 协议&#xff08;http:// 或 https://&#xff09; http: location.pathname 返回当前页面的路径和文件名…

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

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

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

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

scala 数组合并_Scala程序合并两个数组或数组缓冲区

scala 数组合并Scala | 合并两个数组 (Scala | Merging two arrays) Arrays are important data structures in programming and there may arise times when we have two different arrays and we need to merge them into one for processing. This is the case when you nee…

js的navigator对象的使用(浏览器信息)

window.navigator 对象包含有关访问者浏览器的信息。 <!DOCTYPE html> <html> <body> <div id"example"></div><script>txt "<p>Browser CodeName: " navigator.appCodeName "</p>"; txt &…

http中指定使用worker/prefork

注意&#xff1a; 2.4之前版本默认为prefork&#xff0c; 2.4已经变为event模式。三种模式比较&#xff1a; http://www.cnblogs.com/fnng/archive/2012/11/20/2779977.html在编译apache的时候&#xff0c;有一个参数叫做 --with-mpm... 等号后边用于指定那种模式&#xff…

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

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

Java PipedOutputStream flush()方法与示例

PipedOutputStream类flush()方法 (PipedOutputStream Class flush() method) flush() method is available in java.io package. flush()方法在java.io包中可用。 flush() method is used to flush this PipedOutputStream and forces bytes to be written out of any buffered…

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

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

Android 手机发送短信

参照网上的例子&#xff0c;做了一个手机发送短信的app。 有两种发送方法&#xff1a;第一种方法测试没有信息的发送记录&#xff0c;第二种调用的sms的Activity还要自行操作。 首先是xml布局文件代码&#xff1a; <LinearLayout xmlns:android"http://schemas.andro…

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

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

Java Process getOutputStream()方法与示例

流程类的getOutputStream()方法 (Process Class getOutputStream() method) getOutputStream() method is available in java.lang package. getOutputStream()方法在java.lang包中可用。 getOutputStream() method is used to get the output stream of the process and sub-p…

android中requestFocus 以及与setFocusable的区别

<requestFocus /> 标签用于指定屏幕内的焦点View。 例如我们点击tab键或enter键焦点自动进入下一个输入框 用法: 将标签置于Views标签内部 <span style"font-size:14px;"> <EditText id"id/text"android:layout_width"…

韩信大招:一致性哈希

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