为什么克隆方法不应该在正在构建的克隆上调用任何非最终方法
why should a clone method not invoke any nonfinal methods on the clone under construction
我在阅读 effective java 第 2 版时偶然发现了这一段。以下动作何时出现,为什么会产生问题?
Like a constructor, a clone method should not invoke any nonfinal
methods on the clone under construction (Item 17). If clone invokes an
overridden method, this method will execute before the subclass in
which it is defined has had a chance to fix its state in the clone,
quite possibly leading to corruption in the clone and the original.
Therefore the put(key, value) method discussed in the previous
paragraph should be either final or private. (If it is private, it is
presumably the “helper method” for a nonfinal public method.)
注意:我不知道当我们重写方法 a() 并且父构造函数使用方法 a() 时。当我们从子类执行 super() 时,将调用 new a() 而不是父类的 a()。我知道 clone()
会发生同样的问题
仅仅是因为protected
或public
非final方法可以被subclass覆盖,这是初始化问题的根源。假设覆盖方法在初始化之前尝试访问一个字段,你会得到一个 NullPointerException
。假设它调用另一个方法,该方法依赖于在完全初始化之前无法保证的对象的特定状态,这将导致更微妙的错误或程序不正确。
虽然我说的都在你的书里了,所以让我们添加一个具体的例子:
public SpaceShip {
private double oilLevelInLitres;
private String model;
public SpaceShip(double oilLevelInLitres, String model) {
this.oilLevelInLitres = oilLevelInLitres;
displayOilLevel();
this.model = model;
}
public void displayOilLevel() {
System.out.println("Oil level is currently " + oilLevelInLitres + " litres");
}
}
public SpaceShipWithSecondaryReservoir {
public SpaceShip(double oilLevelInLitres, double secondaryReservoirOilLevelInLitres, String oilLevelInLitres) {
super(oilLevelInLitres, oilLevelInLitres);
this.secondaryReservoirOilLevelInLitres = secondaryReservoirOilLevelInLitres;
}
public void displayOilLevel() {
System.out.println("Model " + model + " oil level is currently " + oilLevelInLitres +
" litres and " + secondaryReservoirOilLevelInLitres + " litres in the seconday reservoir");
}
}
public Main() {
public static void main(String[] args) {
// will print "Model null oil level is currently 17.0 litres and 5.0 litres in
// the secondary reservoir"
new SpaceShipWithSecondaryReservoir(17, 5, "Falcon-3X");
}
}
在此示例中,您可能会争辩说父 class 可以在调用该方法之前初始化 model
名称,您是对的,但此刻程序员编写了父 class 构造函数他假设显示方法除了油位之外不需要任何其他状态。
这是一个编程错误,可以通过在构造函数末尾调用显示方法来避免,但在更复杂的情况下,它不会那么微不足道和明显。在构造函数中调用可重写的方法会使您面临 class 的错误,这些错误在您仅调用 final 或私有方法时是不存在的。
我在阅读 effective java 第 2 版时偶然发现了这一段。以下动作何时出现,为什么会产生问题?
Like a constructor, a clone method should not invoke any nonfinal methods on the clone under construction (Item 17). If clone invokes an overridden method, this method will execute before the subclass in which it is defined has had a chance to fix its state in the clone, quite possibly leading to corruption in the clone and the original. Therefore the put(key, value) method discussed in the previous paragraph should be either final or private. (If it is private, it is presumably the “helper method” for a nonfinal public method.)
注意:我不知道当我们重写方法 a() 并且父构造函数使用方法 a() 时。当我们从子类执行 super() 时,将调用 new a() 而不是父类的 a()。我知道 clone()
会发生同样的问题仅仅是因为protected
或public
非final方法可以被subclass覆盖,这是初始化问题的根源。假设覆盖方法在初始化之前尝试访问一个字段,你会得到一个 NullPointerException
。假设它调用另一个方法,该方法依赖于在完全初始化之前无法保证的对象的特定状态,这将导致更微妙的错误或程序不正确。
虽然我说的都在你的书里了,所以让我们添加一个具体的例子:
public SpaceShip {
private double oilLevelInLitres;
private String model;
public SpaceShip(double oilLevelInLitres, String model) {
this.oilLevelInLitres = oilLevelInLitres;
displayOilLevel();
this.model = model;
}
public void displayOilLevel() {
System.out.println("Oil level is currently " + oilLevelInLitres + " litres");
}
}
public SpaceShipWithSecondaryReservoir {
public SpaceShip(double oilLevelInLitres, double secondaryReservoirOilLevelInLitres, String oilLevelInLitres) {
super(oilLevelInLitres, oilLevelInLitres);
this.secondaryReservoirOilLevelInLitres = secondaryReservoirOilLevelInLitres;
}
public void displayOilLevel() {
System.out.println("Model " + model + " oil level is currently " + oilLevelInLitres +
" litres and " + secondaryReservoirOilLevelInLitres + " litres in the seconday reservoir");
}
}
public Main() {
public static void main(String[] args) {
// will print "Model null oil level is currently 17.0 litres and 5.0 litres in
// the secondary reservoir"
new SpaceShipWithSecondaryReservoir(17, 5, "Falcon-3X");
}
}
在此示例中,您可能会争辩说父 class 可以在调用该方法之前初始化 model
名称,您是对的,但此刻程序员编写了父 class 构造函数他假设显示方法除了油位之外不需要任何其他状态。
这是一个编程错误,可以通过在构造函数末尾调用显示方法来避免,但在更复杂的情况下,它不会那么微不足道和明显。在构造函数中调用可重写的方法会使您面临 class 的错误,这些错误在您仅调用 final 或私有方法时是不存在的。