"Variable example might not have been initialized" 匿名 class
"Variable example might not have been initialized" in anonymous class
这个self-answered question was inspired by 。我觉得有更多细节可以更好地与该特定问题分开添加。
为什么下面的代码无法编译?
public class Example {
public static void main(String[] args) {
final Runnable example = new Runnable() {
@Override
public void run() {
System.out.println(example); // Error on this line
}
};
}
}
编译错误:
error: variable example might not have been initialized
发生这种情况是因为匿名 classes 的实现方式。稍微修改一下代码再反编译就可以看到:
final Runnable other = null;
final Runnable example = new Runnable() {
@Override
public void run() {
System.out.println(other);
}
};
即使匿名 class 引用不同的局部变量。现在将编译;我们可以使用 javap
反编译并查看匿名 class:
的接口
final class Example implements java.lang.Runnable {
final java.lang.Runnable val$other;
Example(java.lang.Runnable);
public void run();
}
(Example
是 Java 在内部指代匿名 class 的名称)。
这表明编译器已经为匿名 class 添加了一个构造函数,它接受一个 Runnable
参数;它还有一个名为 val$other
的字段。此字段的名称应暗示此字段与 other
局部变量相关。
你可以深入挖掘字节码,看到这个参数赋值给了val$other
:
Example(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
所以,这表明匿名 classes 从其封闭范围访问变量的方式:它们只是在构造时传递值。
这应该有望说明为什么编译器会阻止您编写问题中的代码:它需要能够将对 Runnable
的引用传递给匿名 class,以便构建它。但是,Java 评估以下代码的方式:
final Runnable example = new Runnable() { ... }
就是先对右边进行全求值,然后赋值给左边的变量。但是,它需要右侧变量的值才能传递到 Runnable
:
的生成构造函数中
final Runnable example = new Example(example);
之前未声明 [=25=] 不是问题,因为这段代码在语义上等同于:
final Runnable example;
example = new Example(example);
所以你得到的错误不是变量无法解析 - 但是,example
在用作构造函数的参数之前没有被赋值,因此编译器错误.
可能有人会争辩说这只是一个实现细节:必须将参数传递给构造函数应该无关紧要,因为无法先调用 run()
方法到作业。
实际上,这不是真的:你可以在赋值之前调用run()
,如下:
final Runnable example = new Runnable() {
Runnable runAndReturn() {
run();
return this;
}
@Override public void run() {
System.out.println(example);
}
}.runAndReturn();
如果允许在匿名class中引用example
,你就可以这样写了。因此,不允许引用该变量。
您可以使用"this"来避免编译错误:
final Runnable example = new Runnable() {
@Override
public void run() {
System.out.println(this); // Use "this" on this line
}
};
为 David 回复添加内容
如果您有多个匿名 类 层并且您需要这样做。
在字段中存储“this”
new AnonClass1(){
private AnonClass1 internalRef=this;
public void methodClass1(){
new AnonClass2(){
public void methodClass2(){
doSomethingWithClass1(internalRef);
}
}
}
}
这个self-answered question was inspired by
为什么下面的代码无法编译?
public class Example {
public static void main(String[] args) {
final Runnable example = new Runnable() {
@Override
public void run() {
System.out.println(example); // Error on this line
}
};
}
}
编译错误:
error: variable example might not have been initialized
发生这种情况是因为匿名 classes 的实现方式。稍微修改一下代码再反编译就可以看到:
final Runnable other = null;
final Runnable example = new Runnable() {
@Override
public void run() {
System.out.println(other);
}
};
即使匿名 class 引用不同的局部变量。现在将编译;我们可以使用 javap
反编译并查看匿名 class:
final class Example implements java.lang.Runnable {
final java.lang.Runnable val$other;
Example(java.lang.Runnable);
public void run();
}
(Example
是 Java 在内部指代匿名 class 的名称)。
这表明编译器已经为匿名 class 添加了一个构造函数,它接受一个 Runnable
参数;它还有一个名为 val$other
的字段。此字段的名称应暗示此字段与 other
局部变量相关。
你可以深入挖掘字节码,看到这个参数赋值给了val$other
:
Example(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
所以,这表明匿名 classes 从其封闭范围访问变量的方式:它们只是在构造时传递值。
这应该有望说明为什么编译器会阻止您编写问题中的代码:它需要能够将对 Runnable
的引用传递给匿名 class,以便构建它。但是,Java 评估以下代码的方式:
final Runnable example = new Runnable() { ... }
就是先对右边进行全求值,然后赋值给左边的变量。但是,它需要右侧变量的值才能传递到 Runnable
:
final Runnable example = new Example(example);
之前未声明 [=25=] 不是问题,因为这段代码在语义上等同于:
final Runnable example;
example = new Example(example);
所以你得到的错误不是变量无法解析 - 但是,example
在用作构造函数的参数之前没有被赋值,因此编译器错误.
可能有人会争辩说这只是一个实现细节:必须将参数传递给构造函数应该无关紧要,因为无法先调用 run()
方法到作业。
实际上,这不是真的:你可以在赋值之前调用run()
,如下:
final Runnable example = new Runnable() {
Runnable runAndReturn() {
run();
return this;
}
@Override public void run() {
System.out.println(example);
}
}.runAndReturn();
如果允许在匿名class中引用example
,你就可以这样写了。因此,不允许引用该变量。
您可以使用"this"来避免编译错误:
final Runnable example = new Runnable() {
@Override
public void run() {
System.out.println(this); // Use "this" on this line
}
};
为 David 回复添加内容
如果您有多个匿名 类 层并且您需要这样做。
在字段中存储“this”
new AnonClass1(){
private AnonClass1 internalRef=this;
public void methodClass1(){
new AnonClass2(){
public void methodClass2(){
doSomethingWithClass1(internalRef);
}
}
}
}