【问题标题】:Why the call to the parent constructor is not the first call in the compiler generated constructor for Inner class?为什么对父构造函数的调用不是编译器为内部类生成的构造函数中的第一次调用?
【发布时间】:2014-08-07 10:11:19
【问题描述】:

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

 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 指定的吗?

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

【问题讨论】:

  • 按照规范,封闭实例可以从超类构造函数中访问。

标签: java inner-classes jls javap


【解决方案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 的方法。为此,必须在调用Foo 的超级构造函数之前设置包含Bar 实例的$this 字段。否则,您最终会得到 NullPointerException,因为该字段尚未初始化。为了更清楚地说明这一点,请查看 Qux 实例的任何实例化的以下调用链:

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

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

【讨论】:

  • 从构造函数调用可覆盖的方法总是会导致看到未初始化的字段,这并不特定于内部类。
  • 是的,但内部类明确允许这样做。但正如我在回答中所说,仍然应该避免这种情况。这通常是设计不佳的标志。
  • 总是允许调用可覆盖的方法,只是被破坏了。我刚刚阅读了 8.8.7.1,但我仍然不明白你的意思。如果您实际上引用 JLS 语句,这将对您的答案质量非常有益,该语句在超级调用之前强制设置封闭实例。
  • 我意识到我为不存在的部分赋予了一些意义。我在 JLS 中实际上找不到对应的注释。
猜你喜欢
  • 2011-09-07
  • 2015-04-24
  • 2013-01-28
  • 2017-08-29
  • 1970-01-01
  • 1970-01-01
  • 2021-05-07
  • 2018-02-28
  • 2011-09-24
相关资源
最近更新 更多