2

考虑以下Test类来演示 Java 中的内部类行为。主要代码在run方法中。休息只是管道代码。

 public class Test {

        private static Test instance = null;

        private Test() {
        }

        private void run() {
            new Sub().foo();  
        }

        public static void main(String[] args) {
            instance = new Test();
            instance.run();
        }

        class Super {
            protected void foo() {
                System.out.println("Test$Super.Foo");
            }
        }

        class Sub extends Super {
            public void foo() {
                System.out.println("Test$Sub.Foo");
                super.foo();
            }
        }
    }

我只是在隐藏Sub构造函数的 javap 输出下方打印:

so.Test$Sub(so.Test);
    Code:
       0: aload_0       
       1: aload_1       
       2: putfield      #1                  // Field this$0:Lso/Test;
       5: aload_0       
       6: aload_1       
       7: invokespecial #2                  // Method so/Test$Super."<init>":(Lso/Test;)V
  10: return   

通常,编译器会确保子类构造函数首先调用超类构造函数,然后再继续初始化它自己的字段。这有助于正确构造对象,但在编译器为内部类生成的构造函数的情况下,我发现规范行为存在偏差。为什么这样?它是由 JLS 指定的吗?

PS:我知道内部类包含对外部类的隐藏引用,并且在上面的 javap 输出中设置了该引用。但问题是为什么它是在调用超级构造函数之前设置的。我错过了什么?

4

1 回答 1

1

内部类是对 Java 程序员应该尽可能透明的抽象。$this考虑以下类结构并考虑如果仅在调用内部类的超级构造函数后设置字段会发生什么。

class Foo {
  Foo() { System.out.println(foo()); }

  String foo() { return "foo"; }
}

class Bar {
  String bar() { return "bar"; }

  class Qux extends Foo {
    @Override 
    String foo() { return bar(); }
  }
}

注意类中被覆盖Qux的方法如何调用其外部类的方法Bar。为此,必须在调用超级构造函数之前设置$this保存实例的字段。否则,您最终会得到 a ,因为该字段尚未初始化。为了更清楚地说明这一点,请查看以下任何实例实例化的调用链:BarFooNullPointerExceptionQux

Qux() -> Foo() -> this.foo() -> $this.bar()

作为一个不熟悉内部类实现的程序员,你会想知道这个异常是从哪里来的。为了使内部类抽象透明,你必须先设置字段,否则你会被一个非常泄漏的抽象卡住。我不认为这会使上面的示例成为一个很好的实现,但它是合法的。

于 2014-08-07T10:21:32.303 回答