当前位置:首页 > > 架构师社区
[导读]今天来聊 final 关键字,因为最近在看的几本书都讲到了 final 关键字,发现好多小细节自己都忽视了,抽空总结了一下,分享给大家。

前言

大家好,我是狂聊君。

今天来聊 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关键字的这8个小细节,你get到几个?


细节三、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,是可以修改成功的。

结论:

  1. 当 final 修饰基本数据类型变量时,不能对基本数据类型变量重新赋值,因此基本数据类型变量不能被改变。
  2. 对于引用类型变量而言,它仅仅保存的是一个引用,final 只保证这个引用类型变量所引用的地址不会发生改变,即一直引用这个对象,但这个对象里面的属性是可以改变的。

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

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

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

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

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

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

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

如下代码:

public class FinalTest { public final void test(){

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

    }
}

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

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

如上代码所示,可以看到会出现 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 之间没有依赖性,所以就有可能会出现下图所示的重排序

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

由于普通变量 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 可能就会出现下图所示的重排序

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

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

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

结束

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


免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭