final
基础使用
-
修饰类
final修饰的类不可以被继承。final类中所有的方法都是隐式final的,因为不能被继承,因此final类中方法肯定不能被复写。
对final类扩展可以使用组合的方式,如对String类扩展,增加一个copy方法:
JDK中常见的final类有:public class StringWrapper { final String realString; // ... // 代理public方法 public int length() { return realString.length(); } // ... /** * 扩展的方法 **/ public String copy() { return new String(realString); } }
- java.lang包,包括基本类型的包装类型(Integer、Double等)、String系列(String、StringBuffer等)、系统类(System、Class等)、数学类(Math、StrictMath)
- java.util包,包括UUID、Optional、Scanner等
- java.lang.reflect包,如Array、Constructor、Field等
- java.time包,Duration、LocalTime等
-
修饰方法
final修饰的方法不能被复写,但是可以被重载。所有private方法都是隐式final的,因为private方法不可以被重写。 - 修饰变量
final修饰的变量为不可变变量,一旦赋值之后,不可以被重新赋值。引用类型final变量,虽然不能对变量本身重新赋值,但是可以修改引用类型的成员变量。
final类型成员变量,必须在声明变量时赋值,或者在构造方法、构造代码块、静态代码块中赋值。
final类型局部变量,必须在声明时赋值。 static final类型的变量可以称为Java中的常量,需要在声明变量或静态代码块中对变量赋初始值。 - 修饰参数
final修饰的参数表明参数不可以被重新赋值。
final与匿名内部类
当匿名内部类访问外部局部变量时,局部变量必须被声明为final。Java 8之后虽然不用显示声明为final,但实际被匿名内部类访问的局部变量仍然是final的。
class OuterClass {
public void foo() {
final int a = 1; // 必须声明为final,否则会编译报错
Runnable r = () -> {
System.out.println("a=" + a);
}
r.run();
}
}
因此JVM的实现中,对于被引用的局部变量,给匿名内部类增加了一个成员变量,将局部变量的值赋值给匿名内部类的成员变量,但是存在成员变量被重新赋值的可能,为了保证安全,强制约定被匿名内部类引用的成员变量必须声明为final类型。
上面的代码等同于下面的代码:
class OuterClass {
public void foo() {
final int a = 1;
Runnable r = new OuterClass$1(a);
r.run();
}
}
class OuterClass$1 implements Runnable {
int a;
OuterClass(int a) {
this.a = a;
}
public void run() {
System.out.println("a=" + a);
}
}
final重排序规则
- 基本数据类型
- 写:禁止final的写重排序到构造方法外面,即保证当对象对其他线程可见时,final变量一定是被初始化过的。
- 读:禁止初次读对象的引用与读该对象的final域变量重排序,即保证读到final变量的引用时,一定会先读包含final变量的引用,从而避免了读到未初始化变量的情况。
- 引用类型
- 增加额外的约束:禁止对“构造方法对final变量的写入”和“将对象引用赋值给引用变量”重排序,避免重新赋值后,取到未赋值的final变量。