如果Java类实现Serializable
接口但没有公共clone()
方法,通常可以创建这样的深层副本:
class CloneHelper {
@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T obj) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
oos.close();
byte[] bytes = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
T copy = (T) ois.readObject();
ois.close();
return copy;
} catch (ClassNotFoundException ex) {
// Shouldn't happen
throw new Error(ex);
} catch (IOException ex) {
// Probably a bug in T's custom serialization methods
throw new RuntimeException(ex);
}
}
}
我经常会遇到像这样的第三方库类,并采用上面那样的黑客攻击。我甚至偶尔会延长ObjectOutputStream
以使副本更浅。它从来没有造成严重的问题,除了效率低(编码/解码速度慢,临时序列化图形可能消耗大量内存。)
如果使用这种技术不安全,那么该类可能不应该被声明为Serializable
。
所以我想知道的是,如果您的班级是Serializable
,可能会阻止您定义公共clone()
方法(使用Cloneable
接口或复制构造函数) ?)
答案 0 :(得分:5)
我更喜欢使用复制构造函数而不是使用上面的机制。您可以更精细地定义要深入或浅层复制的内容,并使对象的复制与对象的序列化不同。复制构造函数可以(例如)允许两个对象共享对主对象的引用,而这可能不适合于通过网络序列化和传输的对象。
请注意,Cloneable
方法现在被广泛认为是破碎的。有关更多信息,请参阅Joshua Bloch的this article。特别是它没有clone()
方法!
答案 1 :(得分:4)
Brian关于Cloneable的观点非常好,但即使Cloneable工作正常,仍然存在一些实例,您可能希望对象可序列化但不可克隆。
如果一个对象在进程范围之外具有唯一标识,就像数据库记录的内存中表示一样,则不希望它是可克隆的,因为这相当于创建具有相同属性的新记录,包括与数据库密钥相关的身份相关属性,这几乎不是正确的事情。同时,出于稳定性或其他原因,您可能会将系统分成多个进程,因此您可能有一个进程与数据库通信并生成这些“ENTITY”对象(请参阅Eric Evans的“Domain-Driven Design”了解更多信息有关在数据支持的应用程序中维护对象标识一致性的信息,但是单独的进程可以使用这些对象来执行业务逻辑操作。实体对象需要是可序列化的,以便将它从一个进程传递到另一个进程。
答案 2 :(得分:2)
我认为Serializable和Cloneable接口应该用于完全不同的目的。如果你有一个复杂的类,那么实现它们并不是那么容易。所以一般情况下它取决于目的。
答案 3 :(得分:1)
嗯,你说序列化机制是一种间接“克隆”对象的方法。这当然不是它的主要功能。它通常用于让程序通过网络传输对象,或者存储并稍后读取它们。您可能期望以这种方式使用对象,并实现Serializable,同时不期望代码在本地克隆对象,并且不实现Cloneable。
代码通过序列化解决这个问题,这表明代码正在以一种作者不想要的方式使用一个对象,这可能是作者或调用者的“错误”,但它并不意味着通用Serializable和Cloneable一起使用。
另外,我不确定clone()是否已经“破坏”,并且不能正确实现。复制构造函数更自然地使用并获得正确的恕我直言。
答案 4 :(得分:1)
Serializable的一个最大问题是它们不容易变得不可变。 序列化迫使你在这里做出妥协。
我很想在对象图中创建复制构造函数。它只是做了一些更咕噜咕噜的工作。
答案 5 :(得分:0)
这让我觉得有点危险,因为序列化有很多陷阱,(虽然大多数都不太可能,但如果我在3d方库中序列化对象,我仍然想测试它们)。不太可能是一个常见的问题,但是有一个带有volatile变量的对象可能是其状态的一部分,可能是克隆操作的一部分(并不是说这是一个很好的设计,只是可能),这样的字段不会在序列化/反序列化过程中被复制。我想到的另一个问题是枚举,常量以及在反序列化过程中如果不处理这些事件而获得此类事物的多个副本的可能性。
再次,边缘情况,但你需要注意的事项。
答案 6 :(得分:0)
我想到了另一个案例 - 当它是enum时。
或更一般地说,当您的班级实施readResolve
时。这意味着从readObject
返回的对象与从流中读取的对象不同,因此它不一定是最初写入流的对象的副本。