可以在超级构造函数完成之前初始化java子类的私有final字段吗?

我有一对看起来像这样的课程;

public abstract class Class1 { //... public Class1() { //... function2(); //... } protected abstract void function2(); } public class Class2 implements Class1 { private final OnSomethingListener mOnSomethingListener = new OnSomethingListener() { @Override onSomething() { doThatOtherThing(); } } protected void function2() { //uses mOnSomethingListener //however mOnSomethingListener is null when this function is called from super() //... } public Class2() { super(); } } 

我假设监听器是null因为我从super()有效地引用它并且它还没有实例化。 但是,我想让它成为final因为它确实如此。 我可以让这个字段(监听器)及时初始化而不将它放在超类中(它不会使用监听器)吗?

您的设计是“泄露this问题”的一个实例,并且是Java中的反模式。 您永远不应该从构造函数调用公开可覆盖的方法。

简短的回答 – 没有。 在子类之前总是调用超类的构造函数,字段初始化器和实例初始化器。

调用顺序在JLS的第8.8.7.1节中正式定义。 总结相关的最后部分(其中S是超类, C是子类):

在确定关于S(如果有的话)的i的直接封闭实例之后,通过从普通方法调用中从左到右评估构造函数的参数来继续评估超类构造函数调用语句。 然后调用构造函数。

最后,如果超类构造函数调用语句正常完成,则执行C的所有实例变量初始值设定项和C的所有实例初始值设定项。 如果实例初始化器或实例变量初始化器I在文本上位于另一个实例初始化器或实例变量初始化器J之前,则在J之前执行。

因此,当超类构造函数运行时,子类及其所有字段都是完全未初始化的。 出于这个原因,从构造函数调用重写方法是不好的做法。 你实际上是从对象构造函数中引用对象“escape”,这意味着构造的所有保证都是关闭的(包括像最终字段改变值等等)。

从构造函数中调用抽象方法几乎总是错误的。 根据子类中方法的实现,在某些情况下你可能会使用它(即如果方法根本不依赖于任何状态),但它几乎肯定会导致难以调试的失败在某一点。

例如,您是否希望在以下方面存在差异:

 protected String function2() { return "foo"; } 

 private final String foo = "foo"; protected String function2() { return foo; } 

分析这样的问题很难,因为它们打破了类如何工作的心理模型。 最好完全避免这种情况。

超类总是在子类之前初始化。 只能初始化子类的静态字段。

您可以从超类调用重写方法,然后访问未初始化的字段。 这被认为是不好的做法。