什么是final关键字
在Java语言中,随着语境的不同final关键字所代表的语义会有一些细微的差异。总的来说,final关键字表达的含义是“禁止修改”,之所以要采用final关键字。
用final关键字修饰的属性,对于Java编译器来说就是一个“常量”。其特点是:1.具体的值在编译期间就已经被确定;2.在运行时不能再被修改。
一般是会出于性能和设计层面的考虑。
禁止修改原理
final相关的两个重排序规则
写规则:
在构造函数中对一个final域的引入,与随后把这个被构造对象的引用赋值给另一个引用变量,这两个操作之间不能重排序。
写final域的重排序规则禁止把final域的写重排序到构造函数之外。这个规则的实 现包含下面2个方面:
1.JMM禁止编译器把final域的写重排序到构造函数之外。
2.编译器会在final域的写之后,构造函数return之前,插入一个StoreStore屏障。这个屏障禁止处理器把final域的写重排序到构造函数之外。
我们知道基本类型和引用类型,它们本身都是存在虚拟机栈中(只有引用类型的实例化对象存在于堆中),final关键字修饰的属性是固定在虚拟机栈内部的存储。引用类型的值是可以改变的。
例如int a = 2;
先在栈中创建a的引用,然后确定栈中是否存在0的值,如果没有存2进来,并指向a。这个时候如果存在int b =2的话,实际在栈中只有一个2,分别指向a,b。final的作用是固定a=2,b=2,不允许引用重新指向别的值。
读规则:
初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。
读final域的重排序规则如下
在一个线程中,初次读对象引用与初次读该对象包含的final域,JMM禁止处 理器重排序这两个操作(注意,这个规则仅仅针对处理器)。编译器会在读 final域操作的前面插入一个LoadLoad屏障。
个人理解:写过的过程中都已近固定数值和禁止修改了,而且是在编译期间被确定,那读的时候一定是确定值。可见性的保证可看我的另一篇文章,《深入理解JMM+Volatile》 https://www.jianshu.com/p/8fd128754b50
可见性代码样例
class FinalFieldExample {
final int x;
int y;
static FinalFieldExample f;
public FinalFieldExample() {
x = 3;
y = 4;
}
static void writer() {
f = new FinalFieldExample();
}
static void reader() {
if (f != null) {
int i = f.x;
int j = f.y;
}
}
}
上面的类展示了final字段应该如何使用。一个正在执行reader方法的线程保证看到f.x的值为3,因为它是final字段。它不保证看到f.y的值为4,因为f.y不是final字段。
同时final修饰的字段能保证构造函数中的执行顺序。
使用方法
class Value {
int v;
public Value(int v) {
this.v = v;
}
}
public class FinalTest {
final int f1 = 1;
final int f2;
public FinalTest() {
f2 = 2;
}
public static void main(String[] args) {
final int value1 = 1;
// value1 = 4;
final double value2;
value2 = 2.0;
final Value value3 = new Value(1);
value3.v = 4;
}
}
上面的例子中,我们先来看一下main方法中的几个final修饰的数据,在给value1赋初始值之后,我们无法再对value1的值进行修改,final关键字起到了常量的作用。从value2我们可以看到,final修饰的变量可以不在声明时赋值,即可以先声明,后赋值。value3时一个引用变量,这里我们可以看到final修饰引用变量时,只是限定了引用变量的引用不可改变,即不能将value3再次引用另一个Value对象,但是引用的对象的值是可以改变的.
public class FinalTest {
/* ... */
public void finalFunc(final int i, final Value value) {
// i = 5; 不能改变i的值
// v = new Value(); 不能改变v的值
value.v = 5; // 可以改变引用对象的值
}
}
如果变量是作为参数传入的,我们怎么保证它的值不会改变呢?这就用到了final的第二种用法,即在我们编写方法时,可以在参数前面添加final关键字,它表示在整个方法中,我们不会(实际上是不能)改变参数的值。
附加内容
final
修饰类:标识类不可以被继承;
修饰方法:不能被重写;
修饰变量:不可以被修改,地址不可以修改,值可以变;
finally:
一定要被执行的代码块;return 之前先执行finally中的代码块
finalize:
java.lang.object中的方法;
GC前调用finalize,释放比较重的资源;
每个对象的finalize方法只会被GC调用一次;