7

如果 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接口或复制构造函数?)


相关:在 Java 中复制对象

4

7 回答 7

5

我宁愿使用复制构造函数而不是使用上述机制。您可以更精细地定义要深度复制或浅复制的内容,并使对象的复制与对象的序列化不同。复制构造函数可能(例如)允许两个对象共享对主对象的引用,而这可能不适用于序列化和通过网络传输的对象。

请注意,该Cloneable方法现在被广泛认为已被破坏。有关更多信息,请参阅Joshua Bloch 的这篇文章。特别是它没有clone()方法!

于 2010-01-24T18:38:59.207 回答
4

Brian 关于 Cloneable 的观点非常好,但即使 Cloneable 工作正常,仍然存在您可能希望对象可序列化但不可克隆的情况。

如果一个对象在进程范围之外具有唯一标识,例如数据库记录的内存表示,您不希望它是可克隆的,因为这相当于创建具有相同属性的新记录,包括标识-数据库键等相关属性,这几乎从来都不是正确的事情。同时,由于稳定​​性或其他原因,您可能将系统分成多个进程,因此您可能有一个进程与数据库通信并生成这些“实体”对象(有关更多信息,请参阅 Eric Evans 的“领域驱动设计”有关在数据支持的应用程序中维护对象身份一致性的信息),但单独的进程可能会使用这些对象来执行业务逻辑操作。

于 2010-01-24T18:47:23.730 回答
2

我认为 Serializable 和 Cloneable 接口应该用于完全不同的目的。如果你有一个复杂的类,那么实现它们中的每一个就不是那么容易了。所以在一般情况下,这取决于目的。

于 2010-01-24T18:50:18.173 回答
1

好吧,您是说序列化机制是间接“克隆”对象的一种方式。这当然不是它的主要功能。它通常用于让程序通过网络传输对象,或存储并稍后读取它们。您可能期望以这种方式使用对象并实现 Serializable,而不期望代码在本地克隆对象,而不是实现 Cloneable。

代码通过序列化解决这个问题的事实表明,代码正在以作者不希望的方式使用对象,这可能是作者或调用者的“错误”,但这并不意味着通常 Serializable 和可克隆一起去。

另外,我不确定 clone() 是否被“破坏”了,因为它很难正确实现。复制构造函数更自然地使用和正确恕我直言。

于 2010-01-24T18:48:25.620 回答
1

Serializable 最大的问题之一是它们不能轻易地变成不可变的。序列化迫使你在这里做出妥协。

我很想在对象图中创建复制构造函数。它只是有点繁重的工作要做。

于 2010-01-24T23:13:21.707 回答
0

这让我觉得有点危险,因为序列化有许多陷阱,(尽管其中大多数不太可能,但如果我在 3d 方库中序列化对象,我仍然想测试它们)。不太可能是一个常见问题,但可能会有一个带有 volatile 变量的对象作为其状态的一部分,这可能是克隆操作的一部分(并不是说这是一个好的设计,只是它是可能的),这样的字段不会在序列化/反序列化过程中被复制。想到的另一个问题是枚举、常量,以及如果您在反序列化期间不处理它们,可能会获得这些东西的多个副本。

同样,边缘情况,但您需要注意的事情。

于 2010-01-24T18:50:33.553 回答
0

我只是想到了另一种情况-当它是enum时。

或者更一般地说,当你的类实现readResolve. 这意味着您返回的对象readObject与从流中读取的对象不同,因此它不一定是最初写入流的对象的副本。

于 2011-10-22T18:02:57.043 回答