如果在构造函数中使用super调用重写方法会发生什么

时间:2015-10-28 17:57:52

标签: java constructor super

有两个班级Super1Sub1

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();
    }
}

当我调用类printThree的方法Sub1时,我希望输出为:

  

打印三   3

因为Sub1构造函数调用了super.printThree();

但实际上我

  

0
  打印三   3

我知道0是int的默认值,但它是如何发生的?

3 个答案:

答案 0 :(得分:15)

你看到了三件事的影响:

  1. 默认的超级构造函数调用,以及

  2. 相对于超级调用的实例初始值设定项,以及

  3. 如何覆盖方法

  4. 您的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 真正的样子,让我们来看看它:

    1. JVM创建实例并将所有实例字段设置为其默认值(所有位都关闭)。因此,此时three字段已存在,且值为0

    2. JVM调用Sub1

    3. Sub1立即致电super()Super1),其中......

      1. ...致电printThree。由于printThree被覆盖,即使对Super1的调用位于Sub1的代码中,也会调用重写的方法(three中的方法) 。这是Java实现多态的一部分。由于three的实例初始化程序尚未运行,0包含Super1,这就是输出的内容。

      2. Sub1返回。

    4. 返回three,编译器插入(重新定位,真正)的three实例初始化代码运行并为Sub1提供新值。

      < / LI>
    5. printThree来电three。由于printThree的实例初始化代码现已运行,3打印javap -c

    6. 关于此实例初始化程序代码被移入构造函数,您可能想知道:如果我有多个构造函数怎么办?代码被移入哪一个?答案是编译器将代码复制到每个构造函数中。 (您也可以在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); } } 中看到它。)(如果您有一个非常复杂的实例初始化程序,如果编译器有效地将其转换为方法,我不会感到惊讶,但我没有看过。)< / p>

      如果你做一些非常顽皮的事情并在你的实例初始化期间调用一个方法,那就更清楚了:(live copy

      pd.read_csv(StringIO(df.to_csv(index=False)),parse_dates=['Time'])
      

      输出:

      Super constructor
      Sub's printThree: 0
      Sub's initThree
      Sub's printThree: 3
      

      注意“Sub的initThree”来自那个序列。

答案 1 :(得分:3)

创建实例时,将调用Sub1构造函数。

任何构造函数中的第一条指令是对超类构造函数的调用。如果您没有显式调用,则会对Super1的no-args构造函数进行隐式调用。

no-args构造函数正在调用this.printThree()。此方法在Sub1中被覆盖。现在,这部分可能会令人困惑,但即使代码在超类中,this.method()仍然引用了重写方法。

因此,它会调用printThree()中的Sub1,其中会打印变量three - 0的未初始化值。

现在超类的构造函数已经完成,它完成Sub1构造函数,它使用super.printThree()。由于它具体说super,因此使用来自Super1的方法而不是覆盖方法。这将打印Print Three

现在Sub1构造函数也已完成,main调用新实例printThree()。现在three已初始化,因此您获得了输出3

答案 2 :(得分:0)

虽然之前的答案给出了明确的答案,但是他们没有给出任何关于如何避免将来出现问题的指示,所以我也想在此添加我的意见。

如果你要继承,那么你应该让超类构造函数尽可能“哑”。例如

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;
    }

非常好,并且会在保持父类功能的同时为您提供最小的副作用。

但是

public Super(){
method1();
method2();
}

然后让sub做这个

public Sub(){
super.method1();
super.method2();
}

真的是在寻找麻烦,并且可能难以追踪错误。对象在初始化期间做的越少越好,因为它为孩子们提供了灵活性。 管理继承就像是愚蠢的经理和聪明的经理。愚蠢的经理称蒂姆和特雷西的员工,因为他们都是员工,他们作为会计师和人力资源经理的工作只是标签。聪明的经理知道Tim和Tracy是会计师和经理,并不在乎他们基本上只是员工。