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 很好地控制了要克隆和不克隆的内容(例如使用 transient
或 String[] 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:深度克隆/复制实例的推荐解决方案的主要内容,如果未能解决你的问题,请参考以下文章