如果在构造函数中使用super调用重写方法会发生什么
有两个类Super1
和Sub1
Super1.class
public class Super1 { Super1 (){ this.printThree(); } public void printThree(){ System.out.println("Print Three"); } }
Sub1.class
public class Sub1 extends Super1 { Sub1 (){ super.printThree(); } int three=(int) Math.PI; public void printThree(){ System.out.println(three); } public static void main(String ...a){ new Sub1().printThree(); } }
当我调用类Sub1
的方法printThree
,我期望输出为:
打印三
3
因为Sub1
构造函数调用了super.printThree();
。
但我真的得到了
0
打印三
3
我知道0是int
默认值但它是如何发生的?
你看到了三件事的影响:
-
默认超级构造函数调用,和
-
实例初始化程序相对于超级调用,和
-
被覆盖的方法如何工作
你的Sub1
构造函数是这样的:
Sub1(){ super(); // <== Default super() call, inserted by the compiler three=(int) Math.PI; // <== Instance initializers are really inserted // into constructors by the compiler super.printThree(); }
(令人惊讶,我知道,但这是真的。使用javap -c YourClass
来看。:-))
看起来这样的原因是超类必须有机会在子类初始化其对象的一部分之前初始化其对象的一部分。 所以你得到了这种交织效果。
鉴于这就是Sub1
真实情况 ,让我们来看看它:
-
JVM创建实例并将所有实例字段设置为其默认值(所有位都关闭)。 所以在这一点上,
three
字段存在,并且值为0
。 -
JVM调用
Sub1
。 -
Sub1
立即调用super()
(Super1
),其中......-
...打电话给
printThree
。 由于printThree
被覆盖,即使对它的调用是在Super1
的代码中,它也是被调用的重写方法(Sub1
)。 这是Java实现多态的一部分。 由于three
实例初始化程序尚未运行,因此three
包含0
,这就是输出的结果。 -
Super1
回归。
-
-
回到
Sub1
,编译器插入(重新定位,真正)的three
实例初始化代码运行并给出three
新值。 -
Sub1
调用printThree
。 由于现在运行three
实例初始化程序代码,printThree
打印3
。
关于此实例初始化程序代码被移入构造函数,您可能想知道:如果我有多个构造函数怎么办? 代码被移入哪一个? 答案是编译器将代码复制到每个构造函数中。 (你也可以在javap -c
看到它。)(如果你有一个非常复杂的实例初始化器,如果编译器有效地将它变成一个方法,我也不会感到惊讶,但我还没看过。)
如果你做一些非常顽皮的事情并在你的实例init期间调用一个方法,那就更清楚一点了:( 实时拷贝 )
class Super { public static void main (String[] args) { new Sub(); } Super() { System.out.println("Super constructor"); this.printThree(); } protected void printThree() { System.out.println("Super's printThree"); } } class Sub extends Super { int three = this.initThree(); Sub() { this.printThree(); } private int initThree() { System.out.println("Sub's initThree"); return 3; } protected void printThree() { System.out.println("Sub's printThree: " + this.three); } }
输出:
超级构造函数 Sub的printThree:0 Sub的initThree Sub的printThree:3
注意“Sub的initThree”在那个序列中的位置。
创建实例时,将调用Sub1
构造函数。
任何构造函数中的第一条指令都是对超类构造函数的调用。 如果没有显式调用,则会对Super1
的no-args构造函数进行隐式调用。
no-args构造函数调用this.printThree()
。 在Sub1
重写此方法。 现在,这部分可能令人困惑,但即使代码在超类中, this.method()
仍然引用了重写方法。
所以它在Sub1
调用printThree()
,它打印变量three
– 0
的未初始化值。
现在超类的构造函数已经完成,它完成了Sub1
构造函数,它使用了super.printThree()
。 因为它特别说super
,所以使用Super1
的方法而不是覆盖的方法。 这将打印出Print Three
。
现在Sub1
构造函数也完成了, main
调用了新实例的printThree()
。 现在已经初始化了three
,所以你得到输出3
。
虽然之前的答案给了你明确的答案,但他们没有给你任何关于如何避免将来出现问题的指示,所以我也想在此加上我的意见。
如果你要inheritance,那么你应该让超类构造函数尽可能“哑”。 例如
public class Super{ private int a,b; public Super(int a, int b) { this.a = a; this.b = b; } //all the methods operating on the data provided by constructor }
然后有像这样的子构造函数
private int c,d; public Sub(int a, int b) { super(a,b); c = a; d = b; }
完全没问题,并且会在保持父类function的同时为您提供最小的副作用。
但有
public Super(){ method1(); method2(); }
然后让sub做这个
public Sub(){ super.method1(); super.method2(); }
真的是在寻找麻烦,并且可能难以追踪错误。 对象在初始化期间做的越少越好,因为它为孩子提供了灵活性。 管理inheritance就像是愚蠢的经理和聪明的经理。 愚蠢的经理称蒂姆和特雷西的员工,因为他们都是员工,他们作为会计师和人力资源经理的工作只是标签。 聪明的经理知道Tim和Tracy是会计师和经理,并不在乎他们基本上只是员工。