这是因为匿名类的实现方式。稍微修改一下代码然后反编译就可以看到这个:
final Runnable other = null;
final Runnable example = new Runnable() {
@Override
public void run() {
System.out.println(other);
}
};
即使匿名类引用不同的局部变量。现在将编译;我们可以使用javap反编译,查看匿名类的接口:
final class Example$1 implements java.lang.Runnable {
final java.lang.Runnable val$other;
Example$1(java.lang.Runnable);
public void run();
}
(Example$1 是 Java 在内部引用匿名类的名称)。
这表明编译器已向匿名类添加了一个构造函数,该构造函数接受Runnable 参数;它还有一个名为val$other 的字段。此字段的名称应该暗示此字段与other 局部变量有关。
你可以进一步挖掘字节码,看到这个参数被赋值给val$other:
Example$1(java.lang.Runnable);
Code:
0: aload_0
// This gets the parameter...
1: aload_1
// ...and this assigns it to the field val$other
2: putfield #1 // Field val$other:Ljava/lang/Runnable;
5: aload_0
6: invokespecial #2 // Method java/lang/Object."<init>":()V
9: return
因此,这表明匿名类从其封闭范围访问变量的方式:它们只是在构造时传递值。
这应该有望说明为什么编译器会阻止您编写问题中的代码:它需要能够将对 Runnable 的引用传递给匿名类才能构造它。但是,Java 评估以下代码的方式:
final Runnable example = new Runnable() { ... }
是先对右侧进行全面求值,然后将其赋值给左侧的变量。但是,它需要右侧变量的值才能传递到Runnable$1的生成构造函数中:
final Runnable example = new Example$1(example);
example 之前没有被声明不是问题,因为这段代码在语义上等同于:
final Runnable example;
example = new Example$1(example);
所以你得到的错误不是变量无法解析 - 但是,example 在用作构造函数的参数之前没有被赋值,因此编译器错误。
可能有人认为这只是一个实现细节:参数必须传递给构造函数并不重要,因为在赋值之前不可能调用run() 方法。
其实不是这样的:你可以在赋值前调用run(),如下:
final Runnable example = new Runnable() {
Runnable runAndReturn() {
run();
return this;
}
@Override public void run() {
System.out.println(example);
}
}.runAndReturn();
如果允许在匿名类中引用example,你就可以这样写。因此,不允许引用该变量。