在父类和子类的构造函数中调用重写的方法

时间:2019-08-10 13:05:33

标签: java polymorphism

我在Java测试中遇到一个问题,但我错了。请向我解释原因。

public class A {
    protected String str = "A";
    public static int i = 1;
    public A() {
        foo();
    }
    public void foo() {
        // TODO Auto-generated method stub
        System.out.println(str + " " + i);
    }
}
public class B extends A {
    protected String str = "B";
    public static int i = 2;
    public B() {
        foo();
    }
    public void foo() {
        // TODO Auto-generated method stub
        System.out.println(str + " " + i);

    }
}
public class Program {
    public static void main(String args[]){

                A a = new B();
                B b = new B();
    }
}

在测试中,他们问我输出是什么? 我回答:“ A 1 B 2 A 1 B 2” 但是正确的答案是:“ null 2 B 2 null 2 B 2” 你能解释为什么吗?

2 个答案:

答案 0 :(得分:0)

对于第A a = new B();行,输出为null 2 B 2,因为在隐式super()完成运行之前,类中的实例字段尚未初始化。

在这种情况下,由于class B正在扩展class A,因此B()构造函数将通过A()隐式调用超类无参数构造函数super(),其中foo()方法被调用。

public A() {                                            <---
    foo(); //overriden version in class B is called        |
}                                                          |
                                                           |
public B() {                                               |
    // Static fields are already initialized               |
    // Instance fields are not yet initialized             |
    // Here super() is called implicitly which calls A() ---
    // Instance fields are now initialized to respective values
    foo();
}

调用foo()中被覆盖的class B,由于foo()class B被覆盖,因为super()尚未完成,实例字段class B中的尚未初始化,而是null。因此,您将看到第一个null 2

然后,当控件从super()中出来时,实例初始化程序将为B运行,然后调用foo()的{​​{1}},此时{{ 1}}初始化为值class B,因此您看到class B已打印。

您看不到静态字段的任何问题,因为它们不依赖于完成B的调用,它们在加载类时已初始化,因此您看到了{{1 }}始终进行初始化和打印,而不是B 2

super()行也是如此。

要注意的重要一点是,您已经在类i中重新定义了null实例字段,因此,当调用覆盖的B b = new B();时,它实际上将引用该实例字段的实例字段。类str而不是类B的字段。

考虑以下代码,我从类foo()中删除了B字段:

A

这将在您重新运行程序时在下面显示输出:

str

之所以发生这种情况,是因为B的{​​{1}}调用了构造函数class A { protected String str = "A"; public static int i = 1; public A() { foo(); } public void foo() { // TODO Auto-generated method stub System.out.println(str + " " + i); } } class B extends A { public static int i = 2; public B() { foo(); } public void foo() { // TODO Auto-generated method stub System.out.println(str + " " + i); } } ,而后者又通过自己的A 2 A 2 A 2 A 2 调用了super()构造函数。在B中完成A()之后,将从Object()继承的super()的实例成员全部初始化。现在,覆盖的super()将打印出从A()继承的B的初始化值。

答案 1 :(得分:0)

new B()将尝试使用B的构造函数创建B的新实例。所有构造函数始终以super()调用开头,即使该调用不存在。除此之外,还有“实例初始化程序”;这些是分配给str变量的表达式。它们不是字符串常量,因为它需要一个静态的最终变量,而str不是。

方法重载,但字段不重载:每个类都有自己的名为str的字段,与另一个str的字段无关。

对象创建的执行顺序如下:

  1. 首先,将所有字段设置为初始值(null / 0 / false)
  2. 从B中的目标构造函数运行super()调用,以确定哪个构造函数用于A(),以及调用父级构造函数所需的参数。
  3. 调用该超级构造函数(它本身也可以自己执行这5步例程!)
  4. 运行此类中的所有实例初始化程序。
  5. 现在继续并运行其余的构造函数。

所以,按顺序:

  1. A.strB.str都设置为null。
  2. A.str = "A"被评估。
  3. A的构造函数运行,并调用foo()。 foo是一种方法,因此使用动态分配。 B覆盖它,它是B的一个实例,因此,运行B的foo()而不是A的foo()。
  4. B的foo运行,并打印B的str,它仍然为空。
  5. B的foo打印其版本的“ i”,即2。因此,null 2
  6. 现在评估B.str = "B"
  7. 现在B的其余构造函数已运行,再次运行B的foo(),但是这次B.str已初始化,因此它打印B 2
  8. 然后我们再做一次;您将实例分配给的变量的类型在这里绝对无效,因此,null 2 B 2将再次打印。