76

是否有任何用于深度克隆 java 集合的实用程序:

  • 数组
  • 列表
  • 地图

注意:更喜欢一些不使用序列化但使用 Object.clone() 方法的解决方案。我可以确定我的自定义对象将实现 clone() 方法,并且将仅使用可克隆的 java 标准类...

4

8 回答 8

65

我认为以前的绿色答案很糟糕,您为什么会问?

  • 它添加了很多代码
  • 它要求您列出要复制的所有字段并执行此操作
  • 使用 clone() 时,这对列表不起作用(这就是 HashMap 的 clone() 所说:返回此 HashMap 实例的浅表副本:键和值本身没有被克隆。)所以你最终手动完成(这使得我哭了)

哦,顺便说一句,序列化也很糟糕,你可能不得不到处添加 Serializable (这也让我哭了)。

那么解决方案是什么:

Java 深度克隆库 克隆库是一个小型的开源(apache 许可证)Java 库,它可以深度克隆对象。对象不必实现 Cloneable 接口。实际上,这个库可以克隆任何 java 对象。如果您不希望修改缓存的对象或想要创建对象的深层副本,则可以在缓存实现中使用它。

Cloner cloner=new Cloner();
XX clone = cloner.deepClone(someObjectOfTypeXX);

在https://github.com/kostaskougios/cloning上查看

于 2009-08-06T20:07:50.177 回答
20

在 Java 中复制对象的所有方法都有严重的缺陷:

克隆

  1. clone() 方法是受保护的,因此您不能直接调用它,除非相关类使用公共方法覆盖它。
  2. clone() 不调用构造函数。任何构造函数。它将分配内存,分配内部class字段(您可以通过 读取getClass())并复制原始字段。

有关 clone() 的更多问题,请参阅 Joshua Bloch 的书“ Effective Java, Second Edition ”的第 11 项

连载

序列化更糟糕;它有许多缺陷,clone()然后是一些缺陷。Joshua 有一个完整的章节,仅此主题就有四个项目。

我的解决方案

我的解决方案是为我的项目添加一个新界面:

public interface Copyable<T> {
    T copy ();
    T createForCopy ();
    void copyTo (T dest);
}

代码如下所示:

class Demo implements Copyable<Demo> {
    public Demo copy () {
        Demo copy = createForCopy ();
        copyTo (copy);
        return copy;
    }
    public Demo createForCopy () {
        return new Demo ();
    }
    public void copyTo (Demo dest)
        super.copyTo (dest);
        ...copy fields of Demo here...
    }
}

不幸的是,我必须将此代码复制到我的所有对象中,但它始终是相同的代码,因此我可以使用 Eclipse 编辑器模板。好处:

  1. 我可以决定调用哪个构造函数以及如何初始化哪个字段。
  2. 初始化以确定的顺序发生(根类到实例类)
  3. 我可以重用现有对象并覆盖它们
  4. 键入安全
  5. 单身人士保持单身人士

对于标准 Java 类型(如集合等),我使用可以复制它们的实用程序类。这些方法有标志和回调,所以我可以控制副本的深度。

于 2009-03-20T13:52:16.887 回答
17

对集合进行浅克隆很容易,但如果您想进行深度克隆,库可能比手动编码更好(因为您也想克隆集合中的元素

就像这个答案一样,我使用了Cloner 库,并专门针对 XStream(它可以通过序列化然后反序列化来“克隆”)和二进制序列化对其进行了性能测试。虽然 XStream 在序列化到/从 xml 方面非常快,但 Cloner 在克隆方面要快得多:

0.0851 毫秒:xstream(通过序列化/反序列化克隆)
0.0223 毫秒:二进制序列化(通过序列化/反序列化克隆)
0.0017 毫秒:克隆器
*克隆一个简单对象(两个字段)的平均时间,并且没有默认的公共构造函数。运行 10,000 次。

除了速度快之外,还有更多选择克隆器的理由:

  1. 执行任何对象的深度克隆(即使是您自己不编写的对象)
  2. 您不必在每次添加字段时都保持您的 clone() 方法是最新的
  3. 您可以克隆没有默认公共构造函数的对象
  4. 与春天一起工作
  5. (优化)不会克隆已知的不可变对象(如 Integer、String 等)
  6. 便于使用。例子:

    cloner.deepClone(anyObject);

于 2009-08-06T20:59:46.780 回答
14

我是 Brad 提出的克隆器库的创建者。这是一种无需编写任何额外代码即可克隆对象的解决方案(无需可序列化对象或 impl clone() 方法)

正如布拉德所说,它非常快,最近我上传了一个更快的版本。请注意,手动实现 clone() 方法将比克隆 lib 更快,但是您需要再次编写大量代码。

Cloner lib 对我来说工作得很好,因为我在一个流量非常大的站点(每天大约 100 万个请求)的缓存实现中使用它。每个请求缓存应该克隆大约 10 个对象。它非常可靠和稳定。但请注意,克隆并非没有风险。该库可以配置为打印它在开发期间克隆的每个类实例。通过这种方式,您可以检查它是否克隆了您认为应该克隆的内容 - 对象图可能非常深,并且可能包含对大量对象的引用。使用克隆库,您可以指示它不要克隆您不想要的对象,即单例。

于 2009-10-27T22:48:13.030 回答
10

深度克隆任意集合的一种通用方法是将其序列化为流,然后将其读回新集合中。您将补充与旧对象没有任何关系的全新对象,而不是相同的副本。

查看Bruno 的答案以获取Apache Commons 序列化实用程序类的链接,如果这是您决定采用的路线,这将非常有帮助。

于 2009-03-20T12:09:29.477 回答
5

一种可能性是使用序列化

Apache Commons 提供SerializationUtils

于 2009-03-20T12:10:01.893 回答
2

我使用过这个克隆库,发现它非常有用。由于它有一些限制(我需要对克隆过程进行更细粒度的控制:应该克隆哪个字段、在什么上下文中以及克隆的深度等),我创建了它的扩展版本。您可以通过在实体类中注释字段来控制字段的克隆。

只是为了了解它,这里是一个示例类:

public class CloneMePlease {
    @Clone(Skip.class)
    String id3 = UUID.randomUUID().toString();

    @Clone(Null.class)
    String id4 = UUID.randomUUID().toString();

    @Clone(value = RandomUUID.class, groups=CustomActivationGroup1.class)
    String id5 = UUID.randomUUID().toString();

    @Clone.List({
            @Clone(groups=CustomActivationGroup2.class, value=Skip.class),
            @Clone(groups=CustomActivationGroup3.class, value=Copy.class)})
    Object activationGroupOrderTest = new Object();

    @Clone(LongIncrement.class)
    long version = 1l;

    @PostClone
    private void postClone(CloneMePlease original, @CloneInject CloneInjectedService service){
         //do stuff with the original source object in the context of the cloned object
         //you can inject whatewer service you want, from spring/guice to perform custom logic here
    }
}

更多细节在这里: https ://github.com/mnorbi/fluidity-cloning

如果需要,还有一个特定于休眠的扩展。

于 2013-04-09T19:28:01.040 回答
0

使用序列化然后反序列化,但请注意,此方法仅适用于没有瞬态字段的 Serializable 类。此外,您的单身人士将不再是单身人士。

于 2009-03-20T12:11:32.793 回答