我有一对看起来像这样的课程;
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
,因为它确实如此。我可以让这个字段(监听器)及时初始化而不将它放在超类中(不会使用监听器)吗?
答案 0 :(得分:5)
您的设计是“泄露的this
问题”的实例,并且是Java中的反模式。你永远不应该从构造函数中调用一个可公开覆盖的方法。
答案 1 :(得分:3)
简短回答 - 不。总是在子类'。
之前调用超类的构造函数,字段初始化器和实例初始化器电话的顺序在section 8.8.7.1 of the JLS中正式定义。总结相关的最后部分(其中S
是超类,C
是子类):
在确定关于S(如果有的话)的i的直接封闭实例之后,通过从普通方法调用中从左到右评估构造函数的参数来继续评估超类构造函数调用语句。然后调用构造函数。
最后,如果超类构造函数调用语句正常完成,则执行C的所有实例变量初始值设定项和C的所有实例初始值设定项。如果实例初始值设定项或实例变量初始值设定项在文本上位于另一个实例初始值设定项或实例变量初始值设定项J之前,则在J之前执行。
因此,当超类构造函数运行时,子类及其所有字段都是完全未初始化的。出于这个原因,从构造函数调用重写方法是不好的做法。你实际上是从对象构造函数中引用对象“escape”,这意味着构造的所有保证都是关闭的(包括像最终字段改变值等等)。
从构造函数中调用 abstract 方法几乎总是错误的。根据子类中方法的实现,在某些情况下你可能会使用它(例如,如果方法根本不依赖于任何状态),但它几乎肯定会导致难以调试的失败在某些时候。
例如,您希望它们之间存在差异:
protected String function2() {
return "foo";
}
和
private final String foo = "foo";
protected String function2() {
return foo;
}
分析这样的问题很难,因为它们打破了类如何工作的心理模型。最好完全避免这种情况。
答案 2 :(得分:2)
超类总是在子类之前初始化。只能初始化子类的静态字段。
您可以从超类调用重写方法,然后访问未初始化的字段。这被认为是不好的做法。