概要
java内部类访问局部变量需要加final这是一个设计问题,并不是一个技术问题。
jdk1.8里面,对局部变量赋初值后不再修改,不需要加final。虽然看起来语法变了,实际上这是个语法糖,因为赋初值后,不再修改,符合final的特征,所以编译器不强制在定义的时候加final。
为什么需要加final
内部类的生命周期可能比局部变量长,或者说内部类的方法在执行时,已经不在创建它时的那个上下文中了。如果方法执行完,局部变量就回收,就会导致空指针问题;如果为内部类单独保存它访问的局部变量的内存,就会加大gc的复杂度。
比如下面这段代码,匿名内部类真正访问a,b两个变量时,已经不在test这个方法上下文里面了。
public class App {
public Comparator<String> test() {
// jdk1.8,赋初值后不修改,可以不加final
int a = 6;
int b = 7;
return new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return a-b;
}
};
}
public static void main(String[] args) {
App app = new App();
Comparator<String> comparator = app.test();
System.out.println(comparator.compare("", ""));
}
}
为了实现这个功能,有两种方案:
A.匿名类中的a,b 是test中a,b两个局部变量的副本,即copy-by-value,与函数传参一样。
这种方式,test方法中a,b修改后,不会影响到匿名类中a,b的副本;这样会导致一致性的问题,所以需要加上final,不允许修改a,b。 这样匿名类中a,b的副本与test中a,b的值是一致的,不容易产生BUG。
B.匿名类中的a,b与test中a,b两个局部变量共享同一处内存,即copy-by-reference。
这种方式,匿名类中的a,b就是test中的局部变量a,b。修改其中一处,都会反映到另外一处上面。
匿名类的生命周期可能比test方法中的局部变量长,所以test方法中的a,b的内存回收不能简单的遵从局部变量的回收(不然test方法结束后,a,b的内存被释放;在main方法中执行内部类的compare时,会导致空指针),从而导致a,b不能在栈上分配内存,这样大大增加了JVM内存结构及gcc的设计难度。
为了降低系统复杂度,JVM选择了A设计。