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
这是为什么?
bar = null
怎么样?
- 为什么还要出现
a.bar = A.bar
?我根本没有实例化 A
。
- 如果出现
A
,为什么在之后B
?
在我开始解释代码执行的每一步之前,您应该了解一些事实:
- 字段引用根据引用类型解析,方法调用在 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()
.
- 在
B
的 foo()
中你得到输出 B.foo(): bar = null
因为 Java 还没有初始化 B
的字段(它构造函数还没有被执行!)并且对象类型的字段默认初始化为null
。
- 现在我们完成了
A()
的构造函数,我们回到 B()
的构造函数。
- 在上述构造函数中,我们再次调用了
foo()
,这又是 B
的 foo()
。但与上次不同的是,B
已正确初始化其字段(在调用 super()
之后),因此您可以获得预期的 B.foo(): bar = B.bar
.
- 现在我们又回到了
main
温暖的怀抱。
- 您访问
a.bar
,因为正如我所说,字段引用是根据引用类型解析的,所以您得到 A
的字段 bar
。
- 最后,出于同样的原因,您调用
a.foo()
再次触发 B
的 foo()
再次打印 b.bar
。
我们完成了! :)
更多参考资料和有价值的阅读材料:
Static and dynamic binding explained
Order of constructor calls
我偶然发现了这段代码。
在实际这样做之前,我试图猜测 运行 它的结果是什么。
当我看到它们时我真的很困惑并且需要一些解释。
这是代码:
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
这是为什么?
bar = null
怎么样?- 为什么还要出现
a.bar = A.bar
?我根本没有实例化A
。 - 如果出现
A
,为什么在之后B
?
在我开始解释代码执行的每一步之前,您应该了解一些事实:
- 字段引用根据引用类型解析,方法调用在 run-time(在 dynamic-fashion 中)期间根据对象类型解析。
super()
被 隐式 放置在 每个 构造函数中,即使你自己不把它放在那里(它不是例如,如果您调用super(int x, int y)
,则会调用。- 从构造函数调用 "override-able" 方法被认为是非常糟糕的做法 - 当我们执行时您会明白为什么。
现在让我们分解你的代码step-by-step:
- 您通过调用其默认构造函数
B()
来实例化B
。 - 正如我之前所说,对
super()
的调用被 隐式地 添加到任何构造函数,因此A()
会立即被调用。 - 在
A()
中调用foo()
,它在 classB
中被覆盖,这就是为什么从B
中调用foo()
. - 在
B
的foo()
中你得到输出B.foo(): bar = null
因为 Java 还没有初始化B
的字段(它构造函数还没有被执行!)并且对象类型的字段默认初始化为null
。 - 现在我们完成了
A()
的构造函数,我们回到B()
的构造函数。 - 在上述构造函数中,我们再次调用了
foo()
,这又是B
的foo()
。但与上次不同的是,B
已正确初始化其字段(在调用super()
之后),因此您可以获得预期的B.foo(): bar = B.bar
. - 现在我们又回到了
main
温暖的怀抱。 - 您访问
a.bar
,因为正如我所说,字段引用是根据引用类型解析的,所以您得到A
的字段bar
。 - 最后,出于同样的原因,您调用
a.foo()
再次触发B
的foo()
再次打印b.bar
。
我们完成了! :)
更多参考资料和有价值的阅读材料:
Static and dynamic binding explained
Order of constructor calls