Java中的局部内部类、匿名内部类和Lambda表达式都存在变量捕获机制
至于具体什么叫变量捕获,我们最后在做总结,先来探讨一下关于局部内部类使用外部类的局部变量时,为什么外部类的局部变量需要加 final 修饰?
public class Outer {
public void outerFunc(int i){
class Inner{
public void innerFunc(){
System.out.println(i);
}
}
new Inner().innerFunc();
}
}
以上代码在jdk1.8之前会报错,但是1.8之后不会报错。编译这个类后发现产生了两个class文件也就是说内部类和外部类各一个class文件,这样就产生了一个问题,调用内部类方法的时候如何访问外部类方法中的局部变量呢?实际上编译后的内部类的构造方法的里面,传了对应的外部类的引用和所有局部变量的形参。这时产生了一个不一致的问题,如果局部变量不设为final,那内部类构造完毕后,外部类的局部变量又改变了那怎么办?比如下列代码就改变了外部类的局部变量。
public class Outer {
public void outerFunc(int i){
class Inner{
public void innerFunc(){
System.out.println(i);
}
}
i = 5;
new Inner().innerFunc();
}
}
这样代码时会造成外部类局部变量和内部类中对应的变量的不一致。导致第5行编译报错
注意内部类编译成class文件与 new 无关,i = 5放在 new Inner() 前后不影响不一致关系,new 在JVM运行class文件时才起效
最后说明一下 jdk1.8 和之前版本对这个规则编译的区别。在 jdk1.8 之前,如果不加 final,编译直接会报错,如果在 jdk1.8 的环境下,会发现我们最开始的 Outer.java 文件编译和运行是完全没有问题的,但是这并不能说明内部类使用的局部变量可以更改了,如果我们给局部变量再赋下值会发现编译又会出现错误。也就是说规则没有改变,只是 jdk1.8 的编译变得更加智能了而已,在局部变量没有重新赋值的情况下,它默认局部变量为 final 型,认为你只是忘记加 final 声明了而已。如果你重新给局部变量改变了值或引用,那就无法默认为 final 了,所以报错。
总结
方法里面若有局部内部类、匿名内部类和Lambda表达式的出现,且在这三者中使用了了包含这三者的方法的局部变量时,这样的局部变量就称为捕获的变量,从Java 8开始,局部内部类、匿名内部类和Lambda表达式都可以访问最终(final)或实际最终(effectively final)的局部变量和参数。
实际最终:在初始化之后值永远不会改变的变量或参数。