难以掌握inheritance权
鉴于Main.java:
public class Main{ public static void main(String[]args){ A a = new B(); a.print(); } } class A{ A() {print();} void print() { System.out.println("A"); } } class B extends A{ int i = 4; void print() { System.out.println(i); } }
结果是:
0
4。
但是,如果a.print引用A类,为什么不输出“A”? 在这样的情况下,我如何知道一种方法何时会被调用? 为什么A的构造函数被调用并且仍在使用B的方法?
由于多态性,调用a.print()
打印4
。 调用的方法取决于a
的运行时类型,即B
什么时候被召唤并不重要; 多态性始终适用。
两次都会调用B
的print
方法。 一次是来自A
的构造函数,它由B
的默认构造函数调用。 另一次是你在main
的显式调用。
第一次打印产生0
而不是4
的原因是因为在调用print
, A
仍在构造中。 也就是说, A
构造函数仍在执行中。 在超类构造函数返回之前,尚未在子类中初始化任何内容,甚至不是变量初始值设定项。 值4
在超类构造函数完成后分配,但在子类构造函数的其余部分完成之前分配。 由于变量初始值设定项尚未运行,因此默认值0
( boolean
null
,对象为null
)是第一次打印时的i
值。
该命令由JLS第12.5节列出:
在作为结果返回对新创建的对象的引用之前,处理指示的构造函数以使用以下过程初始化新对象:
将构造函数的参数分配给此构造函数调用的新创建的参数变量。
如果此构造函数以同一个类中的另一个构造函数的显式构造函数调用(第8.8.7.1节)开头(使用此),则使用这五个相同的步骤计算参数并以递归方式处理该构造函数调用。 如果该构造函数调用突然完成,则此过程突然完成,原因相同; 否则,继续步骤5。
此构造函数不是以同一个类中的另一个构造函数的显式构造函数调用开始的(使用此方法)。 如果此构造函数用于Object以外的类,则此构造函数将以超类构造函数的显式或隐式调用开始(使用super)。 使用这五个相同的步骤评估参数并递归处理超类构造函数调用。 如果该构造函数调用突然完成,则由于同样的原因,此过程突然完成。 否则,继续执行步骤4。
为此类执行实例初始值设定项和实例变量初始值设定项,将实例变量初始值设定项的值按从左到右的顺序分配给相应的实例变量,在这些顺序中,它们以文本方式显示在类的源代码中。 如果执行任何这些初始值设定项导致exception,则不会处理其他初始值设定项,并且此过程会突然完成同样的exception。 否则,继续步骤5。
执行此构造函数的其余部分。 如果该执行突然完成,则由于同样的原因,此过程突然完成。 否则,此过程正常完成。
(大胆强调我的)
这是一个为什么调用可以从构造函数重写的方法的坏主意的示例。 子类状态尚未初始化。
对于重写方法(如示例中的print()), 对象类型决定要调用的方法,而不是引用类型。
B没有任何构造函数,因此除了调用A的构造函数之外,它的默认构造函数不会执行任何操作。
现在,当调用B的默认构造函数时,它调用A的构造函数(请记住, i
仍未设置,因此默认值为0)。 A的构造函数调用print(),现在对象实际上是B,它调用B的print()并且它打印0(记住i
没有设置)。
现在,一旦这些构造函数调用完成,B的代码就会执行,将i设置为0.现在调用print()将再次进入B的print()(因为对象只有B),它将打印4。
“调试是关键”