在调用超类的构造函数之前,有没有办法在Java中初始化子类的成员变量?

我需要这个,因为超类中的构造函数正在调用在子类中重写的方法。 该方法返回一个传递给子类构造函数的值。 但必须在子类构造函数之前调用超类构造函数,因此我没有机会保存传入的值。

从超类构造函数调用重写的方法根本不起作用 – 不要这样做。 超类构造函数必须始终在子类之前完成。 当超类构造函数正在执行时,有问题的对象是超类的(半初始化的)实例,而不是子类! 因此,如果您尝试从构造函数中调用任何重写的函数,则它可能依赖的子类字段尚未初始化(正如您所观察到的那样)。 这是课堂设计的基本事实,并没有解决方法。

正如Effective Java 2nd中所述。 埃德。 (第4章,第17项):

有一个类必须遵守允许inheritance的限制。 构造函数不得直接或间接调用可覆盖的方法。 如果违反此规则,将导致程序失败。 超类构造函数在子类构造函数之前运行,因此在子类构造函数运行之前将调用子类中的重写方法。 如果重写方法依赖于子类构造函数执行的任何初始化,则该方法将不会按预期运行。

如果您可以更改超类实现,请尝试将调用移出构造函数中的虚函数。 实现此目的的一种方法是使用工厂方法:

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这也阻止了进一步的子类化。 但是,不建议inheritance子类化具体类 – 一个用于子类化的类应该是抽象的(在这种情况下使用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"!!! } } 

这是Josh Bloch和Neal Gafter的Java Puzzlers的引用:Traps,陷阱和角落案例

只要构造函数调用已在其子类中重写的方法,就会出现问题。 以这种方式调用的方法始终在实例初始化之前运行,此时其声明的字段仍具有其默认值。 为了避免这个问题, 永远不要直接或间接地从构造函数中调用可覆盖的方法 [EJ Item 15]。 此禁止扩展到实例初始化程序和伪readObjectclone的主体。 (这些方法称为伪辅助结构,因为它们在不调用构造函数的情况下创建对象。)

简而言之:不要做!

此外,本书提出了一个可能的解决方案(略微编辑为通用性):

您可以通过在首次使用时懒惰地初始化字段来解决问题,而不是在创建实例时急切地。

你没有提到是否可以修改超类的构造函数。 如果可以,那么这是可行的。 正如其他人所说的那样,这是不可取的,但是这里有一个人为的例子来做你正在寻找的东西 – 超类调用一个重写的方法,它使用一个传递给子类构造函数的参数:

 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

我知道这个问题。 这不可能。 如果超级class不是你自己的,你没有其他选择,那么你可以做一些坏的黑客。 但不建议这样做。

解决方案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:

如果自己的构造函数已经使用标志运行,请在方法中进行区分。 然后你需要复制具有实际值的超级构造函数的一些东西。

如果这是一个问题,那么您的类层次结构中存在一个基本问题; 超类不能知道任何特定的子类,因为可能有几个,它们甚至可能在运行时动态加载。 这就是为什么构造函数中的任何super()调用都必须在第一行。

作为一般规则,不要在构造函数中调用任何公共方法。