发生这种情况的原因是实现匿名类的方式。如果对代码稍作更改然后反编译,则可以看到以下内容:
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允许在匿名类内部进行引用,则可以编写此代码。因此,不允许引用该变量。



