在 Java 的内部类的机制中,非 static 的内部类可以会持有外部类的 this 引用,因此可以访问外部类的资源,这一点还是挺自然的。
但有一点一直让我觉得很神奇,就是方法中的内部类可以访问方法的 final 形参和 final 本地变量,如下:
import javax.swing.*;
public class FooGUI {
public static void main(String[] args) {
//initialize GUI components
final JFrame jf = new JFrame("Hello world!"); //allows jf to be accessed from inner class body
jf.add(new JButton("Click me"));
// pack and make visible on the Event-Dispatch Thread
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
jf.pack(); //this would be a compile-time error if jf were not final
jf.setLocationRelativeTo(null);
jf.setVisible(true);
}
});
}
}
这份代码取自维基百科,main
方法里创建的匿名内部类中引用了 main 方法里的 JFrame jf
本地变量。客户端编程,像 Android 应用开发中由于常常要设置监听,回调,所以经常会用到这个特性。
那么这个特性是个什么原理呢,为什么能这么做呢,如果这个方法运行完了,它的栈帧就被移除了,里面的本地变量也都被清除了,这时候匿名内部类中再使用这个变量不会有问题吗?原来,在内部类中使用外部方法中的 final 本地变量和 final 形参时,编译器会去“捕获”它们,会存一份拷贝在内部类中,这样的话,即使方法的栈帧被清除了,内部类中仍然保存着它的拷贝,仍然能访问它。
至于,为什么这些变量要被修饰成 final,主要是为了维持一致性。因为实际上内部类和外部方法用的不是同一份变量,但从外部看来,它们还得像是同一份变量,如果允许改动这些变量的话,很可能会出现不一致的情况,所以用 final 修饰它们,让它们是不可变的。
在 Java 8 后,即使本地变量或方法参数不标记成 final,内部类中也能访问它们。但事实上,其实是和用 final 修饰一样的,同样还是不允许修改他们,只是不用 final 修饰而已。如果有修改它们的行为,那么编译时就会报错。这种变量称为 effectively final 变量。
网友评论