“长”是什么意思的问题含糊不清。以下是一些可能的解释:
解读#1:构造函数有很多参数
具有许多参数的构造函数可能会导致可读性差,但存在更好的替代方案。
这里引用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();
请注意,现在参数已命名,您可以按照您想要的任何顺序设置它们,并且您可以跳过您想要保留默认值的参数。这肯定比伸缩构造函数好得多,尤其是当有大量参数属于许多相同类型时。
另见
相关问题
解释#2:构造函数做了很多耗时的工作
如果工作必须在构造时完成,那么在构造函数或辅助方法中执行它并没有太大的区别。但是,当构造函数将工作委托给辅助方法时,请确保它不可被覆盖,因为这可能会导致很多问题。
这里引用了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的初始化,方法得到了错误的值。这几乎肯定会导致错误和错误。
解释#3:构造函数做了很多可以推迟的工作
当某些工作被推迟到真正需要的时候,可以加快对象的构建;这称为延迟初始化。例如,当构造 String 时,它实际上并不计算其哈希码。它只在第一次需要哈希码时才这样做,然后它会缓存它(因为字符串是不可变的,所以这个值不会改变)。
但是,请考虑 Effective Java 2nd Edition,Item 71:明智地使用延迟初始化。延迟初始化可能会导致细微的错误,并且并不总是会产生改进的性能来证明增加的复杂性是合理的。不要过早优化。