2

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

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()并且它尚未实例化。但是,我想成功,final因为,嗯,确实如此。我可以让这个字段(监听器)及时初始化而不把它放在超类中(永远不会使用监听器)吗?

4

3 回答 3

5

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

于 2012-06-29T11:54:31.727 回答
3

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

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

在确定 i 相对于 S(如果有的话)的直接封闭实例之后,超类构造函数调用语句的求值通过从左到右对构造函数的参数求值来进行,就像在普通方法调用中一样;然后调用构造函数。

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

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

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

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

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

private final String foo = "foo";

protected String function2() {
    return foo;
}

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

于 2012-06-29T11:50:29.100 回答
2

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

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

于 2012-06-29T11:51:00.053 回答