我阅读了 Effective Java 书,但不理解解释 Clonable 接口的一段。有人可以解释一下这一段吗:
...程序员假设如果他们扩展一个类并
super.clone
从子类调用,返回的对象将是子类的一个实例。超类可以提供此功能的唯一方法是返回通过调用super.clone
. 如果一个克隆方法返回一个由构造函数创建的对象,那么它将具有错误的类。
谢谢。
我阅读了 Effective Java 书,但不理解解释 Clonable 接口的一段。有人可以解释一下这一段吗:
...程序员假设如果他们扩展一个类并
super.clone
从子类调用,返回的对象将是子类的一个实例。超类可以提供此功能的唯一方法是返回通过调用super.clone
. 如果一个克隆方法返回一个由构造函数创建的对象,那么它将具有错误的类。
谢谢。
首先我要注意clone
的是,它本身就被破坏了,并且考虑到合同非常弱,复制构造函数 likeSheep(Sheep cloneMe)
是一个比 更加优雅的习语。您可能已经知道这一点,因为您正在阅读这本书,但值得将其放在这里。clone
Cloneable
无论如何,回答这个问题:
Object.clone()
将创建一个与调用它的对象相同类型的对象。出于这个原因,强烈建议“级联”到Object
以获得您计划返回的结果。如果有人决定不遵循这个约定,你最终会得到一个违反约定的类类型的对象,这将导致许多问题。
为了说明我有这样的课
class Sheep implements Cloneable {
Sheep(String name)...
public Object clone() {
return new Sheep(this.name); // bad, doesn't cascade up to Object
}
}
class WoolySheep extends Sheep {
public Object clone() {
return super.clone();
}
}
突然间,如果我这样做
WoolySheep dolly = new WoolySheep("Dolly");
WoolySheep clone = (WoolySheep)(dolly.clone()); // error
我会得到一个例外,因为我得到的dolly.clone()
是 a Sheep
,而不是 a WoolySheep
。
我不同意@corsiKa 的回答。从Java5.0开始。Java 支持协变返回类型,因此,clone() 的正确实现应该是:
class Sheep implements Cloneable {
Sheep(String name)...
public Sheep clone() {
return new Sheep(this.name);
}
}
class WoolySheep extends Sheep {
public WoolySheep clone() {
return super.clone(); // compile time error, Type miss match.
}
}
此外,建议的替代复制构造函数不支持多态性。考虑以下示例(复制构造函数不能这样做):
interface Animal implements Cloneable {
String whatAreYou()
}
class Cat implements Animal {
String whatAreYou() {
return "I am a cat";
}
Cat clone() {
return new Cat();
}
}
class Dog implements Animal{
String whatAreYou() {
return "I am a dog";
}
Dog clone() {
return new Dog();
}
}
class Lib {
Animal cloneAnimal(Animal animal) {
return animal.clone();
}
}
class A {
protected Object clone() {
return new A();
}
}
class B extends A implements Cloneable {
public Object clone() {
return super.clone();
}
}
在这里,A
有一个无效的实现,clone
因为这会抛出一个异常:
B obj = (B)(new B()).clone();
相反,A.clone()
必须调用super.clone()
而不是构造函数。Object.clone()
然后将生成一个运行时类型而不是编译时类型的新对象。
然后将任何字段克隆到这个新对象上。如果您已经有一个初始化所有字段的构造函数(如复制构造函数),那么使用构造函数可能很诱人,但这会导致任何子类的行为不正确。
如果类是final
,那么没关系,因为它不能有任何子类。