您现在的位置是:亿华云 > 系统运维
final关键字的这8个小细节,你get到几个?
亿华云2025-10-05 08:29:31【系统运维】9人已围观
简介今天来聊 final 关键字,因为最近在看的几本书都讲到了 final 关键字,发现好多小细节自己都忽视了,抽空总结了一下,分享给大家。正文final关键字是一个常用的关键字,可以修饰变量、方法、类,
今天来聊 final 关键字,键字节因为最近在看的小细几本书都讲到了 final 关键字,发现好多小细节自己都忽视了,键字节抽空总结了一下,小细分享给大家。键字节
正文
final关键字是小细一个常用的关键字,可以修饰变量、键字节方法、小细类,键字节用来表示它修饰的小细类、方法和变量不可改变,键字节下面就聊一下使用 final 关键字的小细一些小细节。
细节一、键字节final 修饰类成员变量和实例成员变量的小细赋值时机
对于类变量:
声明变量的时候直接赋初始值 在静态代码块中给类变量赋初始值如下代码所示:
public class FinalTest { //a变量直接赋值 private final static int a = 1; private final static int b; //b变量通过静态代码块赋值 static { b=2; } }对于实例变量:
在声明变量的时候直接赋值 在非静态代码块中赋值 在构造器中赋初始化值如下代码所示:
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; } @Override public String toString() { return "Student{ " + "age=" + age + ", name=" + name + \ + }; } } } //下面是打印结果 Student{ age=100, name=Java}从打印结果可以看到:引用数据类型变量 student 的 age 属性修改成 100,是可以修改成功的。
结论:
当 final 修饰基本数据类型变量时,不能对基本数据类型变量重新赋值,因此基本数据类型变量不能被改变。 对于引用类型变量而言,它仅仅保存的是一个引用,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 域的写重排序到构造函数之外,这个规则的实现主要包含了两个方面:
JMM 禁止编译器把 final 域的写重排序 到 构造函数 之外 编译器会在 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 关键字容易忽视的一些小细节,看完希望你能有所收获。
很赞哦!(8468)
上一篇: 3、查看排名
相关文章
- 因为域名解析需要同步到DNS根服务器,而DNS根服务器会不定时刷,只有DNS根服务器刷新后域名才能正常访问,新增解析一般会在10分钟左右生效,最长不会超过24小时,修改解析时间会稍微延长。
- 简单介绍 os.path 模块常用方法
- 大前端快闪二:React开发模式 一键启动多个服务
- 十三个优秀的 React JavaScript 框架
- 前面这两个步骤都是在本机完成的。到这里还没有涉及真正的域名解析服务器,如果在本机中仍然无法完成域名的解析,就会真正请求域名服务器来解析这个域名了。
- LongAdder ,这哥们劲儿大
- 赌你看不懂:分布式存储系统的数据强一致性挑战
- 前端以后也要多线程编程了么?
- 为什么起域名意义非凡?起域名有什么名堂?
- 为什么说两个 Integer 数值之间不建议使用 “==” 进行比较