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

Att*_*Kun 9 java oop

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

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()...

  • 即使像这样的 init 方法也是不好的形式。根据我的经验,这样的情况会提出一个问题:您真的应该在这里使用继承吗?答案可能是否定的。 (2认同)

pol*_*nts 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"!!!
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

简而言之:不要做!

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

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