问题描述:为什么匿名内部类和局部内部类只能访问final变量?
代码重现:
public class InnerClassFinalVar {
String ss ="g g s";
public static void main(String[] args) {
int a = 0;
final int f = 1;
String s ="day day up";
new Thread(){
@Override
public void run() {
// System.out.println(a); //Error:(16, 36) java: 从内部类中访问本地变量a; 需要被声明为最终类型
System.out.println(f);
}
}.start();
class LocalInnerClass{
private int v;
public int setV(int a){
v=a;
// System.out.println(s); //Error:(28, 36) java: 从内部类中访问本地变量s; 需要被声明为最终类型
return a;
}
}
new InnerClassFinalVar().print();
System.out.println(new InnerClassFinalVar().ss);
}
private void print() {
class LocalInnerClass {
private int v;
public void print() {
ss+="t";
System.out.println(ss); //可以使用,因为ss的声明周期和外部类一样长
}
}
new LocalInnerClass().print();
}
}
解析:
作用域问题或者说生命周期问题,方法的局部变量的作用域是整个方法,比如上代码中的a, 如果我们可以在匿名内部类new Thread中使用者变量,就可能出现问题;因为有可能在外层方法结束了,但是内部类(为了便于理解,我们以线程为例)仍然在运行用到这个变量,但是这个变量的生命周期已经结束了,这时候内部类就要访问一个不存在的变量,哈哈哈。
fianl解决这个问题是通过复制,当内部类使用这个final变量的时候,就拷贝一份复制品到内部类中,
在编译期间由编译器默认进行,如果这个变量的值在编译期间可以确定,则编译器默认会在匿名内部类(局部内部类)的常量池中添加一个内容相等的字面量或直接将相应的字节码嵌入到执行字节码中。这样一来,匿名内部类使用的变量是另一个局部变量,只不过值和方法中局部变量的值相等,因此和方法中的局部变量完全独立开,
这样内部类就可以一直使用这个元素直到内部类的生命周期结束(
如果局部变量的值在编译期间就可以确定,则直接在匿名内部里面创建一个拷贝。如果局部变量的值无法在编译期间确定,则通过构造器传参的方式来对拷贝进行初始化赋值
)。final修饰的变量有避免了修改或者重新赋值,从而避免了数据不一致的问题。