"变量示例可能尚未初始化"在匿名课堂上

时间:2016-02-04 15:23:47

标签: java anonymous-class

self-answered question的灵感来自Variable 'snackbar' might not have been initialized。我觉得有更多细节可以更好地与特定问题分开添加。

为什么不能编译以下代码?

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

3 个答案:

答案 0 :(得分:8)

这是因为匿名类的实现方式。如果您对代码稍作更改然后反编译,则可以看到这一点:

    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,则可以编写此内容。因此,不允许提及该变量。

答案 1 :(得分:4)

您可以使用“this”来避免编译错误:

final Runnable example = new Runnable() {
  @Override
  public void run() {
    System.out.println(this);  // Use "this" on this line
  }
};

答案 2 :(得分:0)

在 David 回复中添加一些内容

如果您有多个匿名类层并且您需要这样做。

在字段中存储“this”

      new AnonClass1(){
         private AnonClass1 internalRef=this;
    
         public void methodClass1(){
              new AnonClass2(){
                  public void methodClass2(){
                     doSomethingWithClass1(internalRef);
                  }
              }
         }
      }