我在大学里学习OOP(Java),遇到了以下代码:
// This example program aims at inspecting and understanding the order in which the individual
// initialization steps are taken when executing this program.
public class ConstructorTest {
public static void main(String[] args) {
new Sub();
}
}
class Super {
Super() {
System.out.println("Super constructor");
m();
}
void m() {
System.out.println("Method m() of class Super");
}
}
class Sub extends Super {
Person p = new Person();
Sub() {
System.out.println("Sub constructor");
m();
}
@Override
void m() {
System.out.println("Method m() of class Sub");
System.out.println(p.getName());
}
}
class Person {
String name;
String getName() {
return "John Doe";
}
}
代码的输出如下:
"Super constructor"
"Method m() of class Sub"
NullPointerException
发生NullPointerException的原因是无法调用p.getName()
,因为Person p = new Person()
是用null示例化的。(这是故意的!)
程序按以下顺序执行:
1.执行ConstructorTest
类中的main方法。
new Sub()
被调用,这调用了Sub
类的构造函数。Sub
类构造函数开始执行。
1.由于Sub类扩展了Super
类,因此在Sub
类构造函数的主体之前调用超类构造函数Super()
。
1.在Super
类构造函数中:
a)System.out.println("Super constructor");
行将“Super constructor”打印到控制台。
B)调用方法m()
。由于Sub
类覆盖了此方法,因此将调用Sub
类中被覆盖的版本。但是,此时,Sub
类构造函数尚未完成执行。
1.控制返回到Super
类构造函数,构造函数执行完成。
1.控件返回到Sub
类构造函数。
1.遇到行Person p = new Person();
。Person
类构造函数被调用,它没有任何显式输出。
1.创建Person
类的示例并将其分配给p
示例变量。
1.控制返回到Sub
类构造函数,构造函数执行完成。
1.执行Sub
类构造函数中的System.out.println("Sub constructor");
行,将“Sub constructor”打印到控制台。
1.再次调用m()
方法,这次是在Sub
类构造函数中。
1.在Sub
类中被重写的m()
方法内部:
a)执行System.out.println("Method m() of class Sub");
行,将“Method m()of class Sub”打印到控制台。
B)执行System.out.println(p.getName());
行,它调用Person
类的初始化p
示例上的getName()
方法,并将“John Doe”打印到控制台。
我的问题是:
当调用Super
类构造函数时,Java首先将System.out.println("Super constructor");
打印到控制台。在下一步中,它将调用Super.m()
。但是,由于Super.m()
在Sub
类中被覆盖,因此它改为调用Sub.m()
。如果示例化的顺序要求在子类构造函数之前调用超类构造函数,那么Java如何知道Sub
类覆盖了Super
类中的方法m()
?它是否递归地检查每个基类方法是否有同名的子类方法?是否在示例化基类之前“扫描”了Sub类,从而Java知道在示例化时遇到基类方法时如何处理它?
基本上,我想更好地理解当Java重写类方法时,幕后发生了什么。
2条答案
按热度按时间3qpi33ja1#
您假设类元数据的初始化依赖于对象的初始化,但事实并非如此。类在创建该类的第一个示例之前初始化,并且所有示例共享类数据。
例如,当您运行以下程序时
会打印出来的
这表明,即使该类的示例不存在,也知道哪个方法被重写了。(类和方法必须是
public
,本例才能正常工作)在线演示(使用内部类将两个公共类放在一个文件中)
我们还可以证明,无论构造函数的执行如何,每个对象都是其类的示例:
印刷品
执行
new Sub()
时,会发生两件事:Sub
的示例分配内存,内存足够大,可以容纳属于Sub
的所有示例字段(包括继承的字段)。对象的元数据被初始化以指示它是Sub
的示例(考虑指向类元数据的不可更改的指针),并且所有字段被初始化为其类型的默认值(零、false
或null
)。super
构造函数调用之后,但在构造函数的任何其他语句之前。超级构造函数可以调用重写的方法,如前所述,但在构造函数中调用可重写的方法通常不是一个好主意,因为这些重写的方法将看到子类的字段处于其默认状态。
印刷品
在线演示
t9aqgxwy2#
还有一个 * 静态初始化 *。这将在编译器需要资源时发生。
在这种情况下,在下面的声明期间。
因此,步骤如下。
1.Sub 构造函数方法从 main 调用
1.Super 静态初始化,因为 Sub 扩展了 Super
1.Sub 静态初始化,因为它总是在类的初始化之前调用
1.Super 构造函数方法,因为 Sub 扩展了 Super
1.最后,m,来自 Sub,因为您的电话来自 Sub
Sub
类覆盖了Super
类中的方法m()
?..."*例如,为每个类添加一个 * 静态初始化块 *。
输出量