昨日面试美团,被问到 final 关键字,总觉得可以再进行总结一下了。
因为你所掌握的现有知识又会再次颠覆你原来的理解。
final 用于属性
语义:被初始化后不能被更改
class Main {
private final int x;
}
以上代码会报错:x 没有被初始化。
而对没有 final 关键字修饰的属性是会执行默认初始化的。
解决办法:
class Test {
private final int x;
// 1
//private final int x = 0;
// 2
/*Test() {
x = 0;
}*/
// 3
/*Test(int x) {
this.x = x;
}*/
// 4
/*{
x = 100;
}*/
}
在我眼中,没有被 static final
关键字修饰的final
属性必须在实例初始化阶段进行初始化(构造函数或者构造代码块或者定义时),而且初始化后不能再进行赋值。在 JVM 层面该初始化对应于<init>
方法的执行。–>这叫实例初始化。
但是 static final
修饰的属性在准备阶段(加载–>验证–>准备–>解析–>初始化)完成初始化。
public class Main {
private final int x = 100;
}
对应的字节码文件:
但是 Java 中有反射啊!!
package test;
import java.lang.reflect.Field;
class Demo {
final int x = 100;
}
public class Main {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Demo demo = new Demo();
System.out.println(demo.x); // 100
Field x = demo.getClass().getDeclaredField("x");
x.setAccessible(true);
x.setInt(demo, 200);
System.out.println(demo.x); // 100
}
}
因为 x
被 final
修饰,所以我们在调用实例.x
时,都会被优化为 100,因为它是不能在初始化后修改的。提高效率。
以上System.out.println(demo.x);
直接被优化为 System.out.println(100);
以上代码并不能说明反射不能更改 final 属性。因为 final
更多的强调编译阶段
(编译阶段执行检查以及优化)。
验证:
对于访问 final 属性的方法:
final 用于方法
语义:final 方法不能被重写
那么为何这里的调用是 invokevirtual
呢?既然不能被重写,那么就不能构成多态了!那么为何不是 invokespecial
呢?
见 R 大分析:
https://www.zhihu.com/question/45131640
final 用于类
语义:final 类不能被继承
基本类型所对应的包装类型 Byte,Short,Character,Integer,Long,Float,Double 都是 final 类。