关于从构造函数调用可重写方法
简单地说,这是错误的,因为它不必要地为 许多 错误打开了可能性。当调用@Override 时,对象的状态可能不一致和/或不完整。
引自Effective Java 2nd Edition,Item 17: Design and document for inheritance,或者禁止它:
为了允许继承,类必须遵守更多的限制。 构造函数不得直接或间接调用可覆盖的方法。如果您违反此规则,将导致程序失败。超类构造函数在子类构造函数之前运行,因此子类中的覆盖方法将在子类构造函数运行之前被调用。如果覆盖方法依赖于子类构造函数执行的任何初始化,则该方法将不会按预期运行。
这里有一个例子来说明:
public class ConstructorCallsOverride {
public static void main(String[] args) {
abstract class Base {
Base() {
overrideMe();
}
abstract void overrideMe();
}
class Child extends Base {
final int x;
Child(int x) {
this.x = x;
}
@Override
void overrideMe() {
System.out.println(x);
}
}
new Child(42); // prints "0"
}
}
这里,当Base构造函数调用overrideMe时,Child还没有完成对final int x的初始化,方法得到了错误的值。这几乎肯定会导致错误和错误。
相关问题
另见
关于多参数的对象构造
具有许多参数的构造函数可能会导致可读性差,但存在更好的替代方案。
这里引用Effective Java 2nd Edition,Item 2:在面对许多构造函数参数时考虑构建器模式:
传统上,程序员使用伸缩构造函数模式,在该模式中,您提供一个仅具有必需参数的构造函数,另一个具有单个可选参数,第三个具有两个可选参数,依此类推。 ..
伸缩构造函数模式本质上是这样的:
public class Telescope {
final String name;
final int levels;
final boolean isAdjustable;
public Telescope(String name) {
this(name, 5);
}
public Telescope(String name, int levels) {
this(name, levels, false);
}
public Telescope(String name, int levels, boolean isAdjustable) {
this.name = name;
this.levels = levels;
this.isAdjustable = isAdjustable;
}
}
现在您可以执行以下任何操作:
new Telescope("X/1999");
new Telescope("X/1999", 13);
new Telescope("X/1999", 13, true);
但是,您目前不能只设置name 和isAdjustable,而将levels 保留为默认值。你可以提供更多的构造函数重载,但显然这个数量会随着参数数量的增加而爆炸式增长,你甚至可能有多个boolean 和int 参数,这真的会让事情变得一团糟。
如您所见,这不是一个令人愉快的编写模式,使用起来更不愉快(这里的“真”是什么意思?13 是多少?)。
Bloch 建议使用构建器模式,这样您就可以编写如下代码:
Telescope telly = new Telescope.Builder("X/1999").setAdjustable(true).build();
请注意,现在参数已命名,您可以按您想要的任何顺序设置它们,并且您可以跳过您想要保留默认值的那些。这肯定比伸缩构造函数好得多,尤其是当有大量参数属于许多相同类型时。
另见
相关问题