下面是一些示例代码,
class Base
{
private int val;
Base() {
val = lookup();
}
public int lookup() {
//Perform some lookup
// int num = someLookup();
return 5;
}
public int value() {
return val;
}
}
class Derived extends Base
{
private int num = 10;
public int lookup() {
return num;
}
}
class Test
{
public static void main(String args[]) {
Derived d = new Derived();
System.out.println("d.value() returns " + d.value());
}
}
输出:d.value()返回0 //我期望10,因为lookup()被覆盖,但不是0!有人可以澄清这个吗?
Derived
实例变量的初始化在其查找方法执行时尚未发生。如何在调用其方法时确保Derived
的实例变量已初始化?
答案 0 :(得分:8)
首先,由于缺少someLookup
方法,该代码无法编译。
无论如何,除此之外,我认为您的问题是由于构造函数的分层运行方式,您的预期无效。
超类'构造函数总是在子类'之前运行,这包括子类'变量的初始化器(它们实际上作为构造函数的一部分运行)。因此,当您创建Derived
的实例时,会发生以下情况:
Base
构造函数。lookup()
被调用,它使用Derived
。num
返回,这是此时的默认值,因为Derived的构造函数和初始值设定项尚未运行。val
设置为0. Derived
初始值设定项和构造函数 - 从此点开始调用lookup
将返回10. 一般来说,出于这个原因从构造函数调用非final方法是个坏主意,许多静态分析工具会警告你不要这样做。它类似于在构造期间让对象引用泄漏,你最终会得到一个使类级别不变量无效的实例(在你的情况下,Derived的num
是“总是”10但是在某些点它可以被看作是0 )。
编辑:请注意,对于此特定案例,如果没有任何其他代码,您可以通过使num
成为常量来解决问题:
class Derived extends Base
{
private static final int num = 10;
...
这实际上会做你想要的,因为静态初始化器在加载类时运行(必须在调用构造函数之前发生)。但是,这确实适用于:
a)该类的所有实例共享相同的num
变量;
b)num
永远不需要改变(如果这是真的那么(a)自动为真)。
在您给出的确切代码中显然是这种情况,但我希望您可能省略额外的功能以简洁。
我将此包含在这里用于比较和兴趣,而不是因为它是一般意义上的这个“问题”的解决方法(因为它不是)。
答案 1 :(得分:4)
返回0的原因是,在派生中将10分配给num之前,正在调用构造函数Base(并在Derived中调用查找)。
通常,在初始化派生实例字段之前调用基础构造函数。
答案 2 :(得分:2)
在构造函数中调用可以在子类中重写的方法通常是个坏主意。在您的示例中,会发生以下情况:
由于在基础构造函数调用lookup时子类构造函数未完成,因此该对象尚未完全初始化,并且lookup将返回num字段的默认值。
答案 3 :(得分:2)
让我们慢慢来吧:
class Test
{
public static void main(String args[]) {
// 1
Derived d = new Derived();
// 2
System.out.println("d.value() returns " + d.value());
}
}
步骤1,在Derived上调用(默认)构造函数,在设置num = 10之前,它链接到Base的构造函数,该构造函数调用Derived的查找方法,但是num尚未设置,因此val保持未初始化。
步骤2,调用属于Base的d.value(),由于1而未设置val,因此得到0而不是10。
答案 4 :(得分:2)
为什么在构建基类时无法访问子类字段,已经有很多很棒的答案,但我认为你要求如何:a这样的工作解决方案:
public abstract class Animal {
public Animal() {
System.println(whoAmI());
}
public abstract String whoAmI();
}
public Lion() extends Animal {
private String iAmA = "Lion";
public Lion(){super();}
public String whoAmI() {return iAmA;}
}
实用的方法是在基类上引入一个init()方法,从子类的构造函数中调用它,如:
public abstract class Animal {
private boolean isInitialized = false;
public Animal() {}
void init() {
isInitialized = true;
System.out.println(whoAmI());
}
public abstract String whoAmI();
public void someBaseClassMethod() {
if (!isInitialized)
throw new RuntimeException("Baseclass has not been initialized");
// ...
}
}
public Lion() extends Animal {
private String iAmA = "Lion";
public Lion() {
super();
init();
}
public String whoAmI() {return iAmA;}
}
唯一的问题是,您不能强制子类在基类上调用init()
方法,并且可能无法正确初始化基类。但是有了标志和一些例外,我们可以在运行时提醒程序员他应该调用init()
......
答案 5 :(得分:1)
您在lookup()
类中有覆盖方法Derived
,因此当调用Base
构造函数时,它会调用Derived
的方法,其中正文为return num
。在Base
初始化时,num
的{{1}}实例变量尚未初始化且为0.这就是为什么val在Derived
中被赋值为0的原因。
如果我理解您的意图,您应该将Base
中的value
方法更改为:
Base
答案 6 :(得分:1)
当构造函数调用此代码时,下面的代码正在返回0(通过查看程序,你会期望10)。原因很简单,num尚未初始化,而父类调用此方法。
public int lookup() {
return num;
}