Java:深度克隆/复制实例的推荐解决方案

Posted

技术标签:

【中文标题】Java:深度克隆/复制实例的推荐解决方案【英文标题】:Java: recommended solution for deep cloning/copying an instance 【发布时间】:2011-01-10 11:57:00 【问题描述】:

我想知道是否有推荐的方法在 java 中进行实例的深度克隆/复制。

我有 3 个解决方案,但我可能会错过一些,我想听听您的意见

编辑:包括 Bohzo 命题并细化问题:它更多的是关于深度克隆而不是浅层克隆。

自己动手:

在属性之后手动对克隆进行编码,并检查是否也克隆了可变实例。专业人士: - 控制将要执行的操作 - 快速执行缺点: - 繁琐的编写和维护 - 容易出错(复制/粘贴失败、缺少属性、重新分配的可变属性)

使用反射:

使用您自己的反射工具或外部帮助程序(如 jakarta common-beans),很容易编写一个通用的复制方法,可以在一行中完成这项工作。专业人士: - 易于编写 - 无需维护缺点: - 较少控制发生的事情 - 如果反射工具也没有克隆子对象,则可变对象容易出错 - 执行速度较慢

使用克隆框架:

使用为您完成此任务的框架,例如:commons-lang SerializationUtilsJava Deep Cloning LibraryDozerKryo

专业人士: - 与反射相同 - 更好地控制将要克隆的内容。缺点: - 每个可变实例都被完全克隆,即使在层次结构的末尾 - 执行起来可能很慢

使用字节码检测在运行时编写克隆

javassit、BCEL 或 cglib 可用于生成专用克隆器,其速度与单手书写一样快。有人知道为此目的使用这些工具之一的库吗?

我在这里错过了什么? 你会推荐哪一个?

谢谢。

【问题讨论】:

显然 Java 深度克隆库已移至此处:code.google.com/p/cloning 【参考方案1】:

对于深度克隆(克隆整个对象层次结构):

commons-lang SerializationUtils - 使用序列化 - 如果所有类都在您的控制范围内,并且您可以强制实现 Serializable

Java Deep Cloning Library - 使用反射 - 如果您想要克隆的类或对象超出您的控制(第 3 方库)并且您无法让它们实现 Serializable,或者不想执行的情况Serializable

对于浅层克隆(仅克隆第一级属性):

commons-beanutils BeanUtils - 在大多数情况下。

Spring BeanUtils - 如果您已经在使用 spring 并因此在类路径中有此实用程序。

我故意省略了“自己动手”选项 - 上面的 API 很好地控制了要克隆​​和不克隆的内容(例如使用 transientString[] ignoreProperties),因此重新发明***是'不喜欢。

【讨论】:

感谢 Bozho,这很有价值。我同意你关于 DIY 选项的看法!您是否尝试过公共序列化和/或深度克隆库?性能怎么样? 是的,由于上述原因,我已经使用了上述所有选项 :) 当涉及 CGLIB 代理时,只有克隆库有一些问题,并且错过了一些所需的功能,但我认为应该是现已修复。 嘿,如果我的实体已附加并且我有惰性的东西,SerializationUtils 会检查数据库的惰性属性吗?因为这是我想要的,但它没有! 如果你有一个活跃的会话 - 是的,它有。 @Bozho 所以你的意思是如果 bean 中的所有对象都实现可序列化, org.apache.commons.beanutils.BeanUtils.cloneBean(obj) 会做一个深拷贝?【参考方案2】:

Joshua Bloch 的书有一整章题为"Item 10: Override Clone Judiciously",他在其中探讨了为什么在大多数情况下覆盖克隆是一个坏主意,因为它的 Java 规范会产生许多问题。

他提供了一些替代方案:

使用工厂模式代替构造函数:

     public static Yum newInstance(Yum yum);

使用复制构造函数:

     public Yum(Yum yum);

Java 中的所有集合类都支持复制构造函数(例如 new ArrayList(l);)

【讨论】:

同意。在我的项目中,我定义了一个 Copyable 接口,其中包含一个 getCopy() 方法。只需手动使用原型模式。 好吧,我问的不是可克隆接口,而是如何执行深度克隆/复制操作。使用构造函数或工厂,您仍然需要从源创建新实例。 @Guillaume 我认为你在使用深度克隆/复制这个词时需要小心。 java中的克隆和复制并不意味着同样的事情。 Java 规范对此有更多要说的...... OK Java 规范对什么是克隆是准确的......但我们也可以用更常见的含义来谈论克隆......例如,bohzo 推荐的 lib 之一被命名为 ' Java 深度克隆库'... @LWoodyiii 这个newInstance() 方法和Yum 构造函数会做深拷贝还是浅拷贝?【参考方案3】:

自 2.07 版起Kryo supports shallow/deep cloning:

Kryo kryo = new Kryo();
SomeClass someObject = ...
SomeClass copy1 = kryo.copy(someObject);
SomeClass copy2 = kryo.copyShallow(someObject);

Kryo 速度很快,在他们的页面上,您可以找到在生产中使用它的公司列表。

【讨论】:

kryo 如何在没有序列化的情况下进行克隆?它使用反射吗?【参考方案4】:

在内存中使用 XStream toXML/fromXML。速度非常快,已经存在了很长时间并且正在变得强大。对象不需要是可序列化的,并且您没有使用反射(尽管 XStream 可以)。 XStream 可以识别指向同一个对象的变量,并且不会意外地生成实例的两个完整副本。多年来,许多类似的细节已经敲定。我已经使用它很多年了,这是一个很好的选择。它与您想象的一样容易使用。

new XStream().toXML(myObj)

new XStream().fromXML(myXML)

要克隆,

new XStream().fromXML(new XStream().toXML(myObj))

更简洁:

XStream x = new XStream();
Object myClone = x.fromXML(x.toXML(myObj));

【讨论】:

【参考方案5】:

对于复杂的对象,当性能不重要时,我使用gson 将对象序列化为 json 文本,然后反序列化文本以获取新对象。

基于反射的 gson 在大多数情况下都可以工作,除了 transient 字段不会被复制,并且带有循环引用的对象会导致 ***Error

public static <ObjectType> ObjectType Copy(ObjectType AnObject, Class<ObjectType> ClassInfo)

    Gson gson = new GsonBuilder().create();
    String text = gson.toJson(AnObject);
    ObjectType newObject = gson.fromJson(text, ClassInfo);
    return newObject;

public static void main(String[] args)

    MyObject anObject ...
    MyObject copyObject = Copy(o, MyObject.class);


【讨论】:

【参考方案6】:

视情况而定。

为了速度,请使用 DIY。 为了防弹,请使用反射。

顺便说一句,序列化与 refl 不同,因为某些对象可能提供重写的序列化方法(readObject/writeObject)并且它们可能有问题

【讨论】:

反射不是防弹的:它可能会导致您的克隆对象引用您的源...如果源更改,克隆也会更改!【参考方案7】:

我推荐 DIY 方法,结合良好的 hashCode() 和 equals() 方法应该很容易在单元测试中证明。

【讨论】:

好吧,懒惰的我在创建这样的虚拟代码时会咆哮很多。但它看起来像是更明智的道路...... 抱歉,如果没有其他适合您的解决方案,DIY 是的方法......这几乎永远不会【参考方案8】:

我建议覆盖 Object.clone(),首先调用 super.clone(),然后对所有要深度复制的引用调用 ref = ref.clone()。它或多或少是自己动手的方法,但需要的编码更少。

【讨论】:

这是(损坏的)克隆方法的众多问题之一:在类层次结构中,您总是必须调用 super.clone(),这很容易被忘记,这就是为什么我更喜欢使用复制构造函数。【参考方案9】:

对于深度克隆,在你想像这样克隆的每个类上实现 Serializable

public static class Obj implements Serializable 
    public int a, b;
    public Obj(int a, int b) 
        this.a = a;
        this.b = b;
    

然后使用这个函数:

public static Object deepClone(Object object) 
    try 
        ByteArrayOutputStream baOs = new ByteArrayOutputStream();
        ObjectOutputStream oOs = new ObjectOutputStream(baOs);
        oOs.writeObject(object);
        ByteArrayInputStream baIs = new ByteArrayInputStream(baOs.toByteArray());
        ObjectInputStream oIs = new ObjectInputStream(baIs);
        return oIs.readObject();
    
    catch (Exception e) 
        e.printStackTrace();
        return null;
    

像这样:Obj newObject = (Obj)deepClone(oldObject);

【讨论】:

以上是关于Java:深度克隆/复制实例的推荐解决方案的主要内容,如果未能解决你的问题,请参考以下文章

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

反射实现java深度克隆

在Java中深度克隆多维数组......? [复制]

原型模式

Clone() vs Copy constructor-在java中推荐[重复]

java中的浅克隆和深克隆是啥