【问题标题】:"Variable example might not have been initialized" in anonymous class匿名类中的“变量示例可能尚未初始化”
【发布时间】:2016-02-04 15:23:47
【问题描述】:

这个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

【问题讨论】:

    标签: java anonymous-class


    【解决方案1】:

    这是因为匿名类的实现方式。稍微修改一下代码然后反编译就可以看到这个:

        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,你就可以这样写。因此,不允许引用该变量。

    【讨论】:

    • 真的很好!反编译的Example$1 类几乎表明为什么应该otherfinal。如果不是这样,Example$1 可能正在处理other 的过期副本,这至少可以说是……奇怪。
    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      给大卫的回应添加一些东西

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

      在字段中存储“this”

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

      【讨论】:

      • 如果您需要嵌套匿名类并需要引用外部实例,我认为停止使用匿名类可能是值得的。 (无论如何,您可能还想成为final)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-03-25
      • 2015-07-04
      • 1970-01-01
      相关资源
      最近更新 更多