Java 中具有继承和覆盖方法的令人困惑的输出

Perplexing output in Java with inheritance and overriding methods

我偶然发现了这段代码。
在实际这样做之前,我试图猜测 运行 它的结果是什么。 当我看到它们时我真的很困惑并且需要一些解释。
这是代码:

public class A {
    String bar = "A.bar";
    A() { foo(); }

    public void foo() {
        System.out.println("A.foo(): bar = " + bar);
    }
}

public class B extends A {
    String bar = "B.bar";
    B() { foo(); }
    public void foo() {
        System.out.println("B.foo(): bar = " + bar);
    }
}

public class C {
    public static void main(String[] args) {
        A a = new B();
        System.out.println("a.bar = " + a.bar);
        a.foo();
    }
}

输出为:

B.foo(): bar = null
B.foo(): bar = B.bar
a.bar = A.bar
B.foo(): bar = B.bar

这是为什么?

在我开始解释代码执行的每一步之前,您应该了解一些事实:

  • 字段引用根据引用类型解析,方法调用在 run-time(在 dynamic-fashion 中)期间根据对象类型解析。
  • super() 隐式 放置在 每个 构造函数中,即使你自己不把它放在那里(它不是例如,如果您调用 super(int x, int y),则会调用。
  • 从构造函数调用 "override-able" 方法被认为是非常糟糕的做法 - 当我们执行时您会明白为什么。

现在让我们分解你的代码step-by-step:

  • 您通过调用其默认构造函数 B() 来实例化 B
  • 正如我之前所说,对 super() 的调用被 隐式地 添加到任何构造函数,因此 A() 会立即被调用。
  • A() 中调用 foo(),它在 class B 中被覆盖,这就是为什么从 B 中调用 foo() .
  • Bfoo() 中你得到输出 B.foo(): bar = null 因为 Java 还没有初始化 B 的字段(它构造函数还没有被执行!)并且对象类型的字段默认初始化为null
  • 现在我们完成了 A() 的构造函数,我们回到 B() 的构造函数。
  • 在上述构造函数中,我们再次调用了 foo(),这又是 Bfoo()。但与上次不同的是,B 已正确初始化其字段(在调用 super() 之后),因此您可以获得预期的 B.foo(): bar = B.bar.
  • 现在我们又回到了main温暖的怀抱。
  • 您访问 a.bar,因为正如我所说,字段引用是根据引用类型解析的,所以您得到 A 的字段 bar
  • 最后,出于同样的原因,您调用 a.foo() 再次触发 Bfoo() 再次打印 b.bar

我们完成了! :)

更多参考资料和有价值的阅读材料:
Static and dynamic binding explained
Order of constructor calls