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

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

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

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

自己动手:

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

使用反射:

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

使用克隆框架:

使用为您完成此任务的框架,例如:
commons-lang SerializationUtils
Java Deep Cloning Library
Dozer
Kryo

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

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

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

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

谢谢。

【问题讨论】:

  • 显然 Java 深度克隆库已移至此处:code.google.com/p/cloning

标签: java clone


【解决方案1】:

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

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

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

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

我故意省略了“自己动手”选项 - 上面的 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 字段不会被复制,并且带有循环引用的对象会导致 StackOverflowError

    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);

      【讨论】:

        猜你喜欢
        • 2010-10-14
        • 1970-01-01
        • 2014-12-09
        • 2019-02-27
        • 2017-09-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多