关于从构造函数调用可重写方法
简而言之,这是错误的,因为它不必要地为许多错误开辟了可能性。调用 时,对象的状态可能不一致和/或不完整。@Override
引用自 Effective Java 2nd Edition, Item 17: Design and document for inherit,否则禁止它:
类还必须遵守一些限制才能允许继承。构造函数不得直接或间接调用可重写的方法。如果违反此规则,将导致程序失败。超类构造函数在子类构造函数之前运行,因此子类中的重写方法将在子类构造函数运行之前调用。如果重写方法依赖于子类构造函数执行的任何初始化,则该方法的行为将不会按预期方式运行。
下面是一个示例来说明:
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 第 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);
但是,您当前不能仅设置 and ,而将保留为默认值。你可以提供更多的构造函数重载,但显然,随着参数数量的增长,这个数字会爆炸,你甚至可能有多个参数和参数,这真的会让事情变得一团糟。name
isAdjustable
levels
boolean
int
正如你所看到的,这不是一个令人愉快的写作模式,甚至使用起来更不愉快(这里的“真实”是什么意思?13是什么?
Bloch 建议使用生成器模式,这样可以让你编写如下内容:
Telescope telly = new Telescope.Builder("X/1999").setAdjustable(true).build();
请注意,现在参数已命名,您可以按所需的任何顺序设置它们,并且可以跳过要保留为默认值的参数。这当然比伸缩构造函数要好得多,特别是当有大量参数属于许多相同类型时。
另请参见
相关问题