1.final 的简介
final 可以修饰变量, 方法和类, 用于表示所修饰的内容一旦赋值之后就不会再被改变, 比如 String 类就是一个 final 类型的类. 即使能够知道 final 具体的使用方法, 我想对 final 在多线程中存在的重排序问题也很容易忽略, 希望能够一起做下探讨.
2.final 的具体使用场景
final 能够修饰变量, 方法和类, 也就是 final 使用范围基本涵盖了 java 每个地方, 下面就分别以锁修饰的位置: 变量, 方法和类分别来说一说.
2.1 变量
在 java 中变量, 可以分为成员变量以及方法局部变量. 因此也是按照这种方式依次来说, 以避免漏掉任何一个死角.
2.1.1 final 成员变量
通常每个类中的成员变量可以分为类变量 (static 修饰的变量) 以及实例变量. 针对这两种类型的变量赋初值的时机是不同的, 类变量可以在声明变量的时候直接赋初值或者在静态代码块中给类变量赋初值. 而实例变量可以在声明变量的时候给实例变量赋初值, 在非静态初始化块中以及构造器中赋初值. 类变量有两个时机赋初值, 而实例变量则可以有三个时机赋初值. 当 final 变量未初始化时系统不会进行隐式初始化, 会出现报错. 这样说起来还是比较抽象, 下面用具体的代码来演示.
看上面的图片已经将每种情况整理出来了, 这里用截图的方式也是觉得在 IDE 出现红色出错的标记更能清晰的说明情况. 现在我们来将这几种情况归纳整理一下:
类变量: 必须要在静态初始化块中指定初始值或者声明该类变量时指定初始值, 而且只能在这两个地方之一进行指定;
实例变量: 必要要在非静态初始化块, 声明该实例变量或者在构造器中指定初始值, 而且只能在这三个地方进行指定.
2.2.2 final 局部变量
final 局部变量由程序员进行显式初始化, 如果 final 局部变量已经进行了初始化则后面就不能再次进行更改, 如果 final 变量未进行初始化, 可以进行赋值, 当且仅有一次赋值, 一旦赋值之后再次赋值就会出错. 下面用具体的代码演示 final 局部变量的情况.
现在我们来换一个角度进行考虑, final 修饰的是基本数据类型和引用类型有区别吗?
final 基本数据类型 VS final 引用数据类型
通过上面的例子我们已经看出来, 如果 final 修饰的是一个基本数据类型的数据, 一旦赋值后就不能再次更改, 那么, 如果 final 是引用数据类型了? 这个引用的对象能够改变吗? 我们同样来看一段代码.
当我们对 final 修饰的引用数据类型变量 person 的属性改成 22, 是可以成功操作的. 通过这个实验我们就可以看出来当 final 修饰基本数据类型变量时, 不能对基本数据类型变量重新赋值, 因此基本数据类型变量不能被改变. 而对于引用类型变量而言, 它仅仅保存的是一个引用, final 只保证这个引用类型变量所引用的地址不会发生改变, 即一直引用这个对象, 但这个对象属性是可以改变的.
宏变量
利用 final 变量的不可更改性, 在满足一下三个条件时, 该变量就会成为一个 "宏变量", 即是一个常量.
使用 final 修饰符修饰;
1. 在定义该 final 变量时就指定了初始值;
2. 该初始值在编译时就能够唯一指定.
3. 注意: 当程序中其他地方使用该宏变量的地方, 编译器会直接替换成该变量的值
2.2 方法
重写?
当父类的方法被 final 修饰的时候, 子类不能重写父类的该方法, 比如在 Object 中, getClass()方法就是 final 的, 我们就不能重写该方法, 但是 hashCode()方法就不是被 final 所修饰的, 我们就可以重写 hashCode()方法. 我们还是来写一个例子来加深一下理解:
先定义一个父类, 里面有 final 修饰的方法 test();
然后 FinalExample 继承该父类, 当重写 test()方法时出现报错, 如下图:
通过这个现象我们就可以看出来被 final 修饰的方法不能够被子类所重写.
重载!!!
可以看出被 final 修饰的方法是可以重载的. 经过我们的分析可以得出如下结论:
1. 父类的 final 方法是不能够被子类重写的
2. final 方法是可以被重载的
2.3 类
当一个类被 final 修饰时, 表名该类是不能被子类继承的. 子类继承往往可以重写父类的方法和改变父类属性, 会带来一定的安全隐患, 因此, 当一个类不希望被继承时就可以使用 final 修饰. 还是来写一个小例子:
3.final 的例子
final 经常会被用作不变类上, 利用 final 的不可更改性. 我们先来看看什么是不变类.
不变类
不变类的意思是创建该类的实例后, 该实例的实例变量是不可改变的. 满足以下条件则可以成为不可变类:
1. 使用 private 和 final 修饰符来修饰该类的成员变量
2. 提供带参的构造器用于初始化类的成员变量;
3. 仅为该类的成员变量提供 getter 方法, 不提供 setter 方法, 因为普通方法无法修改 fina 修饰的成员变量;
4. 如果有必要就重写 Object 类 的 hashCode()和 equals()方法, 应该保证用 equals()判断相同的两个对象其 Hashcode 值也是相等的.
JDK 中提供的八个包装类和 String 类都是不可变类, 我们来看看 String 的实现.
可以看出 String 的 value 就是 final 修饰的, 上述其他几条性质也是吻合的.
4. 多线程中你真的了解 final 吗?
上面我们聊的 final 使用, 应该属于 Java 基础层面的, 当理解这些后我们就真的算是掌握了 final 吗? 有考虑过 final 在多线程并发的情况吗? 在 java 内存模型中我们知道 java 内存模型为了能让处理器和编译器底层发挥他们的最大优势, 对底层的约束就很少, 也就是说针对底层来说 java 内存模型就是一弱内存数据模型. 同时, 处理器和编译为了性能优化会对指令序列有编译器和处理器重排序. 那么, 在多线程情况下, final 会进行怎样的重排序? 会导致线程安全的问题吗? 下面, 就来看看 final 的重排序.
PS: 暂时这样 最近没时间些 结果东西一多 好多自己还没明白, 抱歉了各位大佬们.
4.1 final 域重排序规则
4.1.1 final 域为基本类型
来源: http://www.jianshu.com/p/196a4aa7f029