我需要这个,因为超类中的构造函数正在调用在子类中重写的方法。该方法返回一个传递给子类构造函数的值。但必须在子类构造函数之前调用超类构造函数,因此我没有机会保存传入的值。
答案 0 :(得分:17)
从超类构造函数调用重写方法根本不起作用 - 不要这样做。超类构造函数必须始终在子类之前完成。当超类构造函数正在执行时,有问题的对象是超类的(半初始化的)实例,而不是子类!因此,如果您尝试从构造函数调用任何重写的函数,它可能依赖的子类字段尚未初始化(正如您所观察到的那样)。这是课堂设计的一个基本事实,并没有解决方法。
如Effective Java 2nd. Ed.(第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;
}
}
请注意Subclass
的构造函数是私有的,以确保它只能通过createInstance()
实例化,因此实例总是正确初始化。 OTOH这也阻止了进一步的子类化。但是,不建议继承子类化具体类 - 一个要进行子类化的类应该是抽象的(在这种情况下使用protected
构造函数)。当然,任何进一步的子类还必须具有非公共构造函数和静态工厂方法,这些方法努力地调用init()
...
答案 1 :(得分:6)
您的问题由以下摘录汇总:
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"!!!
}
}
这是引自Josh Bloch和Neal Gafter的 Java Puzzlers:陷阱,陷阱和角落案例:
只要构造函数调用已在其子类中重写的方法,就会出现问题。以这种方式调用的方法始终在实例初始化之前运行,此时其声明的字段仍具有其默认值。为了避免这个问题,永远不会直接或间接地从构造函数调用可覆盖的方法[EJ Item 15]。此禁止扩展到实例初始化器和伪辅助器
readObject
和clone
的主体。 (这些方法称为伪结构,因为它们在不调用构造函数的情况下创建对象。)
简而言之:不要做它!
此外,本书提出了一个可能的解决方案(略微编辑一般性):
您可以通过在首次使用时懒惰地初始化字段来解决问题,而不是在创建实例时急切地。
答案 2 :(得分:1)
您没有提及是否可以修改超类的构造函数。如果可以,那么这是可行的。正如其他人所说的那样,这是不可取的,但是这里有一个人为的例子来做你正在寻找的东西 - 超类调用一个重写的方法,该方法使用传递给子类的构造函数的参数:
public abstract class Base {
public Base(int value) {
init(value);
System.out.println(getValue());
}
public abstract void init(int value);
public abstract int getValue();
}
public class Derived extends Base {
private int value;
public Derived(int value) {
super(value);
}
public void init(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static void main(String[] argv) {
new Derived(25);
}
}
> java派生的 25
答案 3 :(得分:0)
我知道这个问题。这不可能。如果超级班不是你自己的,你没有其他选择,那么你可以做一些坏的黑客。但不建议这样做。
解决方案1:
使用具有同步的静态工厂。这看起来像(减少):
private static String savedValue;
public static Abc create(String value){
synchronized(Abc.class){
savedValue = value;
Abc abc = new Abc();
savedValue = null;
return abc;
}
private Abc(){
}
private value;
String getValue(){
if( value == null ){
value = savedValue;
}
return value;
}
解决方案2:
如果自己的构造函数已经使用标志运行,则在方法中进行区分。然后你需要复制具有实际值的超级构造函数的一些东西。
答案 4 :(得分:0)
如果这是一个问题,那么您的类层次结构中存在根本问题;超类不能知道任何特定的子类,因为可能有几个,它们甚至可能在运行时动态加载。这就是为什么构造函数中的任何super()调用都必须在第一行。
作为一般规则,不要在构造函数中调用任何公共方法。