Java 难以把握继承权

Java 难以把握继承权,java,Java,给定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;

给定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.a引用类a,为什么不打印输出“a”?在这种情况下,我如何知道何时调用一个方法而不是另一个?为什么调用了A的构造函数,但仍在使用B的方法?

调用
A.print()
打印
4
。调用的方法取决于
a
的运行时类型,即
B
。什么时候叫都没关系;多态性总是适用的

这两次都调用了
B
print
方法。Once来自
A
的构造函数,该构造函数由
B
中的默认构造函数调用。另一次是在
main
中显式调用

第一次打印产生
0
而不是
4
的原因是,在调用
print
时,
A
仍在构造中。也就是说,
A
构造函数仍在执行中。在超类构造函数返回之前,子类中尚未初始化任何内容,甚至变量初始值设定项也未初始化。值
4
在超类构造函数完成之后,但在子类构造函数的其余部分完成之前分配。由于变量初始值设定项尚未运行,
0
的默认值(对于
boolean
s为
false,对于对象为
null
)是第一次打印时的
i

此订单由以下列表列出:

在返回对新创建对象的引用作为结果之前,将使用以下过程处理指示的构造函数以初始化新对象:

  • 为此构造函数调用将构造函数的参数分配给新创建的参数变量

  • 如果此构造函数以同一类中另一个构造函数的显式构造函数调用(§8.8.7.1)开始(使用此构造函数),则使用相同的五个步骤递归地评估该构造函数调用的参数和过程。如果构造函数调用突然完成,那么这个过程也会因为同样的原因突然完成;否则,继续执行步骤5

  • 此构造函数不会以同一类中另一个构造函数的显式构造函数调用开始(使用此函数)。如果此构造函数用于对象以外的类,则此构造函数将以超类构造函数的显式或隐式调用(使用super)开始。使用相同的五个步骤递归地评估超类构造函数调用的参数和过程。如果构造函数调用突然完成,那么此过程也会因为同样的原因突然完成。否则,继续执行步骤4

  • 为此类执行实例初始值设定项和实例变量初始值设定项,将实例变量初始值设定项的值按从左到右的顺序分配给相应的实例变量,这些值在类的源代码中以文本形式出现。如果执行这些初始值设定项中的任何一个会导致异常,则不会再处理其他初始值设定项,并且此过程会在该异常的情况下突然完成。否则,继续执行步骤5

  • 执行此构造函数主体的其余部分。如果该执行突然完成,则此过程出于相同的原因突然完成。否则,此过程将正常完成

  • (粗体强调)


    这就是为什么调用可以从构造函数重写的方法是个坏主意的一个例子。子类状态尚未初始化。

    对于被重写的方法(如示例中的print()),对象类型决定要调用的方法,而不是引用类型。

    B没有任何构造函数,因此其默认构造函数除了调用A的构造函数外不会执行任何操作

    现在,当调用B的默认构造函数时,它调用A的构造函数(请记住,
    i
    仍然没有设置,所以默认值为0)。A的构造函数调用print(),现在由于对象实际上是B的,所以它调用B的print()并打印0(请记住
    i
    未设置)

    现在,一旦这些构造函数调用完成,B的代码就会执行,将i设置为0。现在调用print()将再次获得B的print()(因为对象仅属于B),它将打印4


    “调试是关键”

    a.print
    不引用类a,a只是引用类型
    B
    的类型
    a
    的对象。这是一个很好的例子,说明了为什么我们应该避免构造函数中的多态方法。为了避免此类问题,只使用私有、静态或最终的方法。