我需要这个,因为超类中的构造函数正在调用在子类中重写的方法.该方法返回一个传递给子类构造函数的值.但必须在子类构造函数之前调用超类构造函数,因此我没有机会保存传入的值.
Pét*_*rök 17
从超类构造函数调用重写的方法根本不起作用 - 不要这样做.超类构造函数必须始终在子类之前完成.当超类构造函数正在执行时,有问题的对象是超类的(半初始化的)实例,而不是子类!因此,如果您尝试从构造函数调用任何重写的函数,它可能依赖的子类字段尚未初始化(正如您所观察到的那样).这是课堂设计的基本事实,并没有解决方法.
正如Effective Java 2nd中所解释的那样.埃德.(第4章,第17项):
一个类必须遵守允许继承的限制.构造函数不得直接或间接调用可覆盖的方法.如果违反此规则,将导致程序失败.超类构造函数在子类构造函数之前运行,因此在子类构造函数运行之前将调用子类中的重写方法.如果重写方法依赖于子类构造函数执行的任何初始化,则该方法将不会按预期运行.
如果您可以更改超类实现,请尝试将调用移出构造函数中的虚函数.实现此目的的一种方法是使用工厂方法:
class Super {
public void init() { ... }
}
class Subclass extends Super {
private Subclass() { super(); ... }
public void init() { super.init(); ... }
public static Subclass createInstance() {
Subclass instance = new Subclass();
instance.init();
return instance;
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,构造函数Subclass是私有的,以确保它只能通过实例化createInstance(),因此实例始终正确初始化.OTOH这也阻止了进一步的子类化.但是,不建议继承子类化具体类 - 一个要进行子类化的类应该是抽象的(protected在这种情况下使用构造函数).当然,任何进一步的子类还必须具有非公共构造函数和静态工厂方法,这些方法努力地称之为init()...
您的问题由以下代码段汇总:
abstract class Base {
Base() {
dontdoit();
}
abstract void dontdoit();
}
public class Child extends Base {
final int x;
Child(int x) {
this.x = x;
}
@Override void dontdoit() {
System.out.println(x);
}
public static void main(String args[]) {
new Child(42); // prints "0" instead of "42"!!!
}
}
Run Code Online (Sandbox Code Playgroud)
这是Josh Bloch和Neal Gafter的Java Puzzlers的引用:陷阱,陷阱和角落案例:
只要构造函数调用已在其子类中重写的方法,就会出现问题.以这种方式调用的方法始终在实例初始化之前运行,此时其声明的字段仍具有其默认值.为了避免这个问题,永远不要直接或间接地从构造函数中调用可覆盖的方法 [EJ Item 15].这一禁令延伸到实例初始化和pseudoconstructors的尸体
readObject和clone.(这些方法称为伪辅助结构,因为它们在不调用构造函数的情况下创建对象.)
简而言之:不要做!
此外,本书提出了一个可能的解决方案(略微编辑为通用性):
您可以通过在首次使用时懒惰地初始化字段来解决问题,而不是在创建实例时急切地.