可以创建java子类';在超级构造函数完成之前,是否初始化的私有final字段?

可以创建java子类';在超级构造函数完成之前,是否初始化的私有final字段?,java,null,super,construction,Java,Null,Super,Construction,我有一对这样的班级 public abstract class Class1 { //... public Class1() { //... function2(); //... } protected abstract void function2(); } public class Class2 implements Class1 { private final OnSomethingListener

我有一对这样的班级

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();
    }
}

我假设侦听器为空,因为我正在从
super()
有效地引用它,而且它还没有实例化。然而,我想让它成为最终的,因为,嗯,它是。我可以让这个字段(监听器)在不将其放入超类(永远不会使用监听器)的情况下及时初始化吗?

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

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

在确定与S(如果有的话)相关的立即封闭的i实例之后,超类构造函数调用语句的评估通过从左到右评估构造函数的参数进行,就像在普通方法调用中一样;然后调用构造函数

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

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

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

例如,您是否希望以下各项之间存在差异:

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


分析这样的问题很难,因为它们打破了课堂运作的思维模式。最好完全避免这种情况。

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


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

您的设计是“泄漏的
问题”的实例,并且是Java中的反模式。永远不要从构造函数调用可公开重写的方法。

拥有正确的行为应该胜过“尽可能使用
final
”。我只会使用一个getter来初始化字段。这使得初始化顺序非常清晰和明确,而不依赖于技术性。或者,您可以在构造函数之后运行第二个初始化函数,并调用
function2(),但是我仍然不认为这是一个比丢弃
final
更好的选择。一个超类将在它的一个子类被初始化之前被初始化。@Jean Christophertin:如果超类需要子类直接管理的对象,这是一个很好的解决方案。在这种情况下,OP似乎希望超类不知道
function2()到底是什么
不运行或需要运行。一种典型的解决方法是分两步进行,首先实例化,然后初始化。如果您使用Wicket之类的东西,而Wicket依靠构造函数进行所有对象初始化,则这往往是一个问题。(我能理解这种情绪,不得不使用单独的初始值设定器感觉很不自然。)我在安卓系统上,我需要将
视图
添加到
视图组
层次结构中,有时我还需要添加一些额外的层次结构。我已经通过在子构造函数中使用回调修复了实际问题,但是我可以理解为什么这可能是个坏主意,是的@millimoose正确的方法是工厂方法,可以从外部将其视为单个初始化点。如果Wicket不支持这一点,那就是软弱的表现。@MarkoTopolnik,或者是在不值得权衡的情况下,没有将API表面的每一个模式都抛出的表现。“泄漏<代码>此问题”的发生频率不够高,并且可以通过惰性初始化器轻松缓解,因此不希望使API变得支离破碎和不直观是一种有效的设计选择。
private final String foo = "foo";

protected String function2() {
    return foo;
}