前言

大家好,我是狂聊君。

今天来聊 final 关键字,因为最近在看的几本书都讲到了 final 关键字,发现好多小细节自己都忽视了,抽空总结了一下,分享给大家。

正文

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 关键字容易忽视的一些小细节,看完希望你能有所收获。

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:

长按订阅更多精彩▼如有收获,点个在看,诚挚感谢

final关键字的这8个小细节,你get到几个?相关推荐

  1. final 关键字的 8 个小细节

    作者 | 狂聊君   责编 | 张文 头图 | CSDN 下载自视觉中国 今天来聊 final 关键字,因为最近在看的几本书都讲到了 final 关键字,发现好多小细节自己都忽视了,抽空总结了一下,分 ...

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

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

  3. final关键字细节

    final关键字在java中是一个很重要的关键字,其实按照其字面意思理解,就可以一窥这个关键字端倪,final的本意是最终的.所谓最终的,其最重要的特征就是不能修改,由此衍生出的许多细节均应以这个特征 ...

  4. java学习中,instanceof 关键字 和 final 关键字、值的传递(java 学习中的小记录)...

    java学习中,instanceof 关键字 和 final 关键字.值的传递(java 学习中的小记录)作者:王可利(Star·星星) instanceof 关键字 作用: 1.用来判断某个对象是否 ...

  5. JAVASE零基础入门——package、封装、继承、访问权限修饰符、super、重写、Javabean 和 final 关键字

    目录 一.Package 二.封装 三.继承 四.访问权限修饰符 五.super 关键字 六.重写 6.1 重写 与 重载 之间的区别 : 6.2 重写的实现条件: 6.3 重写的需求 : 6.4 调 ...

  6. 3.4—4 Java final关键字、接口、数组

    文章目录 一.final 关键字 final修饰属性.方法.类 二.接口 (interface. implement) 1.什么是接口? 1)生活中的接口----USB接口 2)面向接口编程 3)接口 ...

  7. 如何用C语言改变宏定义的大小,C语言中宏定义使用的小细节

    C语言中宏定义使用的小细节 #pragma#pragma 预处理指令详解 在所有的预处理指令中,#Pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作.#p ...

  8. Java学习总结:11(final关键字)

    final关键字 在Java中final称为终结器,在Java中可以使用final定义类.方法和属性. 一.使用final定义的类不能再有子类,即:任何类都不能继承以final声明的父类. 在设计类的 ...

  9. final关键字用法

    Java关键字final有"这是无法改变的"或者"终态的"含义,它可以修饰非抽象类.非抽象类成员方法和变量. final类不能被继承,没有子类,final类中的 ...

最新文章

  1. PHP安装包解压失败,php安装失败,说什么解压失败什么的
  2. Apache Ivy 2.5.0-rc1发布–现在允许解析器超时
  3. RSS阅读器使用:ROME,Spring MVC,嵌入式Jetty
  4. python3捕获异常_python中异常捕获方法详解
  5. 天啦噜!知道硬盘很慢,但没想到比 CPU Cache 慢 10000000 倍
  6. 禁用select下拉方法
  7. python sorted list 元组 多列排序
  8. 《Phper》MySQL 的一些基本操作
  9. java反射基础_Java反射基础 - havie的笔记 - 记笔记 - 私塾在线 - 只做精品视频课程服务...
  10. smartdeblur(图片模糊处理工具) v2.2
  11. Java核心技术卷1 如何阅读?
  12. Win8怎么在桌面显示我的电脑
  13. 如何解压.bz2文件包
  14. HTTP协议和SOCKS5协议
  15. 数字集群通信知识与应用
  16. 测试管理工具【禅道】使用详解
  17. 数据结构之树的操作大全
  18. LyricsX for Mac(桌面歌词显示器)
  19. 在线付费问诊互联网医院智慧医疗系统包含哪些功能
  20. vscode上的git三部曲+git pull操作

热门文章

  1. IPinfoga查询地理位置
  2. 人工智能改善客户服务体验
  3. 发文件服务器空间满,针对服务器储存空间爆满而引发的宕机问题的解决方案
  4. c语言 8155 数码管,基于8155的8LED显示串口通信机设计 编程
  5. HDU1042(高精度模拟乘法)
  6. 图论 ---- E. Pairs of Pairs(构造+无向图的dfs树的性质)
  7. 小H和游戏(无根树转有根树,思维)难度⭐⭐⭐★
  8. poj2724(二分图匹配)
  9. 嵌入式linux dhcp移植,嵌入式linux中的dhcp服务器
  10. 完全相同的4个小矩形如图所示放置_3个数学难题——走进传奇数学天才的数字游戏...