【发布时间】:2011-05-04 04:04:44
【问题描述】:
我想知道以下内容:
-
Cloneable表示我们可以拥有对象的克隆或副本,通过 实现Cloneable接口。有什么优点和 这样做有什么坏处? - 如果对象是 复合对象?
【问题讨论】:
我想知道以下内容:
Cloneable 表示我们可以拥有对象的克隆或副本,通过
实现Cloneable 接口。有什么优点和
这样做有什么坏处?【问题讨论】:
关于Cloneable,您首先应该知道的是 - 不要使用它。
用Cloneable实现克隆是非常困难的,不值得。
使用其他选项代替,例如 apache-commons SerializationUtils(深度克隆)或 BeanUtils(浅克隆),或者简单地使用复制构造函数。
See here 了解 Josh Bloch 关于使用Cloneable 进行克隆的观点,这解释了该方法的许多缺点。 (Joshua Bloch 是 Sun 的员工,领导了众多 Java 功能的开发。)
【讨论】:
static 方法,所以只需提供一个static WhatEverTheInterface copy(WhatEverTheInterface initial)?但我想知道这会给您带来什么,因为您在克隆时从对象复制字段,但接口仅定义方法。需要解释一下吗?
因此,请谨慎使用 Cloneable。与您为做好每一件事所付出的努力相比,它并没有给您带来足够的好处。
【讨论】:
A) 与复制构造函数相比,克隆并没有太多优势。最大的可能是创建完全相同动态类型的新对象的能力(假设声明的类型是可克隆的并且具有公共克隆方法)。
B) 默认克隆创建一个浅拷贝,除非你的克隆实现改变了它,否则它将保持一个浅拷贝。这可能很困难,尤其是如果您的课程有最终字段
Bozho 是对的,克隆很难做到正确。复制构造函数/工厂将满足大多数需求。
【讨论】:
不幸的是,Cloneable 本身只是一个标记接口,也就是说:它没有定义 clone() 方法。
所做的是更改受保护的 Object.clone() 方法的行为,该方法将为未实现 Cloneable 的类抛出 CloneNotSupportedException,并为实现的类执行成员级浅拷贝。
即使这是您正在寻找的行为,您仍然需要实现自己的 clone() 方法才能将其公开。
当实现你自己的clone()时,想法是从super.clone()创建的对象开始,它保证是正确的类,然后做任何额外的字段填充以防浅拷贝不是你想要的。从 clone() 调用构造函数会有问题,因为如果子类想要添加自己的额外可克隆逻辑,这会破坏继承;如果要调用 super.clone() 在这种情况下会得到一个错误类的对象。
这种方法会绕过任何可能在您的构造函数中定义的逻辑,这可能会出现问题。
另一个问题是任何忘记覆盖 clone() 的子类将自动继承默认的浅拷贝,这可能不是你想要的可变状态(现在将在源和副本之间共享)。
出于这些原因,大多数开发人员不使用 Cloneable,而是简单地实现一个复制构造函数。
有关 Cloneable 的更多信息和潜在陷阱,我强烈推荐 Joshua Bloch 的《Effective Java》一书
【讨论】:
克隆是一种基本的编程范式。 Java 可能在很多方面实现得很糟糕,这一事实并没有减少对克隆的需求。而且,很容易实现克隆,无论你希望它如何工作,浅的、深的、混合的,等等。如果你愿意,你甚至可以为函数使用名称 clone 而不是实现 Cloneable。
假设我有 A、B 和 C 类,其中 B 和 C 派生自 A。如果我有一个 A 类型的对象列表,如下所示:
ArrayList<A> list1;
现在,该列表可以包含 A、B 或 C 类型的对象。您不知道这些对象是什么类型。所以,你不能像这样复制列表:
ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
list2.add(new A(a));
}
如果对象实际上是 B 或 C 类型,您将不会得到正确的副本。而且,如果 A 是抽象的怎么办?现在,有人提出了这个建议:
ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
if(a instanceof A) {
list2.add(new A(a));
} else if(a instanceof B) {
list2.add(new B(a));
} else if(a instanceof C) {
list2.add(new C(a));
}
}
这是一个非常非常糟糕的主意。如果添加新的派生类型怎么办?如果 B 或 C 在另一个包中并且您在此类中无权访问它们怎么办?
你想做的是:
ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
list2.add(a.clone());
}
很多人已经指出为什么克隆的基本 Java 实现存在问题。但是,通过这种方式很容易克服:
A类:
public A clone() {
return new A(this);
}
B 类:
@Override
public B clone() {
return new B(this);
}
在 C 类中:
@Override
public C clone() {
return new C(this):
}
我没有实现 Cloneable,只是使用相同的函数名。如果你不喜欢它,就给它起个名字吧。
【讨论】:
Cloneable 的缺点是什么?
如果要复制的对象具有组合,则克隆非常危险。在这种情况下,您需要考虑以下可能的副作用,因为克隆会创建浅拷贝:
假设您有一个对象来处理与数据库相关的操作。比如说,该对象具有Connection 对象作为属性之一。
所以当有人创建originalObject 的克隆时,正在创建的对象,比如说cloneObject。
这里originalObject 和cloneObject 对Connection 对象持有相同的引用。
假设originalObject 关闭了Connection 对象,那么现在cloneObject 将无法工作,因为connection 对象在它们之间共享并且实际上被originalObject 关闭。
如果假设您要克隆一个具有 IOStream 作为属性的对象,可能会出现类似的问题。
如果对象是复合对象,递归克隆如何发生?
Cloneable 执行浅拷贝。意思是原始对象和克隆对象的数据将指向相同的引用/内存。 相反,深拷贝的情况下,将原始对象内存中的数据复制到克隆对象的内存中。
【讨论】:
Cloneable 不执行复制,Object.clone 执行。 “将原始对象内存中的数据复制到克隆对象的内存”正是Object.clone 所做的。您需要谈论引用对象的内存来描述深度复制。