深度克隆实用程序推荐[关闭]


74

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

  • 数组
  • 清单
  • 地图

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



使用克隆库为我节省了一天! github.com/kostaskougios/cloning
Gaurav

Answers:


65

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

  • 它添加了很多代码
  • 它要求您列出所有要复制的字段并执行此操作
  • 当使用clone()时,这对于Lists无效(这是HashMap的clone()所说的:返回此HashMap实例的浅表副本:键和值本身未被克隆。)因此您最终需要手动进行操作(这使得我哭了)

哦,顺便说一句,序列化也很糟糕,您可能必须在各处添加Serializable(这也让我哭了)。

那么解决方案是什么:

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

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

https://github.com/kostaskougios/cloning中查看


4
Cloner是一个很棒的库(只是它的性能有时会让我哭泣……但是我想您不能用反射做得更好)
mik01aj 2010年

此解决方案看起来更好。还没有尝试过,但是此线程中有一些对克隆器非常正面的评论。
尤拉吉

1
在Android设备上无法使用...
wieczorek1990

先前的绿色答案是什么?
DaveFar 2013年

1
这个库有一个大问题。如果像这样使用deppClone(),则会发生太多复制!!!我的意思是,如果从该枚举中克隆出一个字段之后,如果您的“枚举”在其主体中声明了“抽象”方法,并强制每个常量对该抽象方法提供不同的实现,则==不再适用于那些常量!这很容易引起问题...库中有一些解决方案。registerFastCloner()和registerImmutable()都可以作为解决方案,还没有尝试过...而且我也不知道它是否可以解决!
Mostafa Zeinali 2014年

20

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

克隆

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

有关clone()的更多问题,请参见约书亚·布洛赫(Joshua Bloch)的书“有效的Java,第二版”的第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类型(例如集合等),我使用可以复制它们的实用程序类。这些方法具有标志和回调,因此我可以控制副本的深度。


2
我通过在需要克隆的所有类中实现clone()来执行类似的操作。最大的问题是,如果我有收藏,则必须对其进行迭代并自行复制...
Juraj,2009年

使用一个接受Collection并返回ArrayList的帮助器函数:由于您知道大小,因此将只分配一次内存,而ArrayList对于通常的访问速度很快。
亚伦·迪古拉09年

createForCopy需要返回一个演示
TimP

您是否考虑显示Eclipse编辑器模板?
Michal

17

浅克隆的集合是很容易的,但如果你想深克隆,因为要克隆的元素库可能会做你比手更好的编码它(里面集合为好)。

就像这个答案一样,我使用了Cloner库,并针对XStream(可以先序列化然后反序列化“克隆”)和二进制序列化对它进行性能测试。尽管XStream可以非常快速地从xml序列化到/从xml序列化,但是Cloner的克隆速度要快得多:

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

除了快速之外,还有更多选择克隆器的原因:

  1. 对任何对象(甚至是那些您自己没有写过的对象)执行深层克隆
  2. 您不必在每次添加字段时都保持最新的clone()方法
  3. 您可以克隆没有默认公共构造函数的对象
  4. 与Spring合作
  5. (优化)不会克隆已知的不可变对象(例如Integer,String等)
  6. 易于使用。例:

    cloner.deepClone(anyObject);


14

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

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

Cloner lib对我来说效果很好,因为我在流量非常大(每天约100万个请求)的站点的缓存实现中使用它。缓存应为每个请求克隆大约10个对象。这是相当可靠和稳定的。但是请注意,克隆并非没有风险。可以将lib配置为println在开发期间克隆的每个类实例。这样,您可以检查它是否克隆了您认为应该克隆的对象-对象图可能非常深,并且可以包含对大量对象的引用。使用clone lib,您可以指示它不要克隆不需要的对象,即单例。


图书馆看起来不错,请稍后再检查...
Juraj

“尝试使用克隆库,您可以指示它不要克隆您不想要的对象”,但是我不能,我如何要求它不克隆某些字段?
Sudarshan

问候康斯坦丁诺斯。我正在使用您的库而不是Apache utils库(SerializableUtils的强制转换存在问题)。您的工作没有问题。做得好!。
will824 2015年

我已经使用ur lib很长时间了,这真的很棒!谢谢!D:
Adams.H,2015年

10

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

请查看Bruno的答案,以获取指向Apache Commons序列化实用程序类的链接,如果这是您决定采用的方法,这将非常有用。


序列化解决方案很好,但是我考虑了一些没有它的事情。我可以保证我的自定义对象将使用clone()方法正确进行深度克隆,但是我想要帮助它的对象将用于标准Java类...
Juraj,2009年

克隆的序列化方式很好,但是我有一些不受控制且不可序列化的字段...
Juraj,2009年


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

还有一个休眠特定的扩展,以备不时之需。


0

先使用序列化再进行反序列化,但要注意,这种方法仅适用于无瞬态字段的Serializable类。此外,您的单身人士将不再是单身人士。


在运行时或在程序的单独运行之间使用序列化和反序列化来克隆内存对象是一个坏主意。有关为何这样做的更多信息,请google:“为什么序列化和反序列化不好”。
埃里克·莱斯钦斯基
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.