MySuper
类是父类,由MySub
类扩展。我正在创建对象MySub mySub = new MySub()
。我以为输出会是“y”,但是当我尝试运行代码时,它显示null。
class MySuper {
String strl = "x";
public MySuper() {
myMethod();
}
void myMethod() {
System.out.print(strl);
}
}
class MySub extends MySuper {
String str2 = "y";
void myMethod() {
System.out.print(str2);
}
public static void main(String[] args) {
MySub mySub = new MySub();
}
}
MySub
使用的隐式构造函数如下所示:
class MySub extends MySuper {
String str2;
MySub() {
super();
str2 = "y";
}
// rest omitted for brevity
}
虽然内联字段初始化在技术上已移至构造函数中,但了解其本身并不一定重要。重要的部分是在初始化子类的字段之前调用超级构造函数。因此,当超级构造函数调用被子类重写的 myMethod()
方法时,
str2
字段尚未被分配非默认值。这会导致您的代码打印出来 null
。这就是为什么你应该避免在构造函数中调用可重写的方法。
在你的例子中。当您使用默认构造函数实例化
MySub
实例时,这会隐式执行对超类构造函数的调用。在超类的构造函数中,有一个对
myMethod()
的调用,它在 MySuper
中声明并在 MySub
中重写。现在,即使 myMethod
是从超类的构造函数中调用的,这可能会让我们误以为正在调用
myMethod
的超类版本,但这并不是实际发生的情况。执行所有这些调用的实例仍然是一个 MySub
实例;因此,Java 调用的
myMethod
实现是最接近 MySub
的实现,即 myMethod
被 MySub
覆盖。那么,如果在这种情况下,超类构造函数中调用的 myMethod
版本是被
MySub
覆盖的版本,为什么它不打印 str2
值呢?这是因为,虽然方法调用是动态调度的,但字段是静态调度的。在Java中,只有方法可以覆盖其基类的方法。子类的字段只能影子基类的字段(具有相同的名称)。因此,Java 在编译时已经知道哪些字段正在被使用,并且“仅”在运行时知道正在调用哪些方法实现。这意味着当您在子类中使用字段时,您可以确保您使用的字段始终是您所引用的字段。回到你的例子,由于 myMethod
是从超类的构造函数中调用的,此时我们仍在初始化父对象,子类的字段尚未初始化,它们仍然包含默认值(引用的默认值为 null)。因此,在动态调度的 myMethod
中,我们正在打印静态调度的
str2
的值,该值仍为 null,因为子类初始化尚未发生。小错误:
由于扩展了 MySuper 类,因此出现
class MySuper {
String strl = "x";
public MySuper() {
myMethod();
}
void myMethod() {
System.out.print(strl);
}
}
// class MySub extends MySuper {
class MySub {
String str2 = "y";
void myMethod() {
System.out.print(str2);
}
public static void main(String[] args) {
MySub mySub = new MySub();
// extra line added
mySub.myMethod();
MySuper ms=new MySuper();
}
}