final关键字深入理解--Java进阶

什么是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调用一次;

你可能感兴趣的:(final关键字深入理解--Java进阶)