中国开源软件网

当前位置: 首页 > 数码 >

写final域,关键字的,8,个小细节

时间:2021-01-27 18:43来源:互联网 作者:小狐

privatefinalstaticintb;//b变量通过静态代码块赋值staticb= 2。

对于实例变量:

在非静态代码块中赋值

在构造器中赋初始化值

如下代码所示:

publicclassFinalTest{//c变量在在时直接赋值privatefinalintc = 1;privatefinalintd;privatefinalinte;//d变量在非静态代码块中赋值{d= 2;}//e变量在构造器中赋值FinalTest{e= 3;}}

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

答:会出现错误。

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

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

写final域,关键字的,8,个小细节(图1)

final 修饰基本类型变量和引用类型变量的区别

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

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

看下面的代码:

publicclassFinalTest//在final实例成员变量时进行赋值privatefinalstaticStudent student = newStudent 50, “Java”;publicstaticvoidmainString args//对final引用数据类型student进行更改student.age = 100;System.out.printlnstudent.toString

staticclassStudentprivateintage;privateString name。

publicStudent( intage, String name){this.age = age;this.name = name;}

@OverridepublicString toString{return“Student{”+“age=”+ age +“ name=”“+ name + ”+“}”}}}

//下面是打印结果Student{age= 100, name= “Java”}

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

结论:

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

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

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

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

写final域,关键字的,8,个小细节(图2)

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

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

如下代码:

publicclassFinalTest{publicfinalvoidtest{}//重载方法不会出现问题publicfinalvoidtest(String test){

}}

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

写final域,关键字的,8,个小细节(图3)

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

final 修饰类的场景

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

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

写 final 域的重排序规则,你知道吗?

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

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

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

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

publicclassFinalTestprivateinta; //普通域privatefinalintb; //final域privatestaticFinalTest finalTest。

publicFinalTest{a = 1; // 1. 写普通域b = 2; // 2. 写final域}

publicstaticvoidwriter{finalTest = newFinalTest;}

publicstaticvoidreaderFinalTest demo = finalTest; // 3.读对象引用inta = demo.a; //4.读普通域intb = demo.b; //5.读final域

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

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

写final域,关键字的,8,个小细节(图4)

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

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

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

读 final 域的重排序规则,你知道吗?

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

还是上面那段代码

publicclassFinalTestprivateinta; //普通域privatefinalintb; //final域privatestaticFinalTest finalTest。

publicFinalTest{a = 1; // 1. 写普通域b = 2; // 2. 写final域}

publicstaticvoidwriter{finalTest = newFinalTest;}

publicstaticvoidreaderFinalTest demo = finalTest; // 3.读对象引用inta = demo.a; //4.读普通域intb = demo.b; //5.读final域

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

写final域,关键字的,8,个小细节(图5)

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

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

结束

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

本文相关词条概念解析:

修饰

修饰,拼音为xīushì,英文为decorate修改润饰,使文字生动。

赋值

将某一数值赋给某个变量的过程,称为赋值。在计算机程序设计语言中,用一定的赋值语句去实现变量的赋值。赋值自W.克鲁尔于20世纪30年代初提出以后,赋值理论广泛应用于代数数论、类域论以及代数几何等方面;到了60年代,它又与泛函分析有着日益增长的关联。除Г本身外的所有孤立子群,按包含关系所成全序集的序型定义为Г的阶。赋值理论也可以从拓扑代数的角度来研究,是基于下述事实。对于有绝对值φ的域F,所有形如{α∈F|φ(α)

网友评论

相关文章