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


175

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

我有3个解决方案,但我可能会想念一些,我想征求您的意见

编辑:包括Bohzo命题,并提炼问题:更多是关于深克隆而不是浅克隆。

自己做:

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

使用反射:

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

使用克隆框架:

使用适合您的框架,例如:
commons-lang SerializationUtils
Java深度克隆库
Dozer
Kryo

优点:
-与反射相同
-可以精确控制要克隆的内容。
缺点:
-每个可变实例都被完全克隆,即使在层次结构的末尾
-可能执行起来很慢

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

可以使用javassitBCELcglib尽快生成专用克隆。有人知道为此目的使用这些工具之一的库吗?

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

谢谢。


1
Java深度克隆库显然移到了这里:code.google.com/p/cloning
Mr_and_Mrs_D 2012年

Answers:


154

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

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

  • Java Deep Cloning Library(使用反射),在您要克隆的类或对象超出您的控制范围时(第3方库),并且您无法使其实现Serializable,或者在您不想实现的情况下Serializable

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

我特意省略了“自己动手”选项-上面的API提供了对克隆内容和不克隆内容的良好控制(例如,使用transientString[] ignoreProperties),因此不建议重新发明轮子。


感谢Bozho,这很有价值。我同意您关于DIY选项的意见!您是否尝试过公共序列化和/或深度克隆库?那性能呢?
纪尧姆

是的,出于上述原因,我使用了上述所有选项:)当涉及CGLIB代理时,仅克隆库存在一些问题,并且缺少某些所需的功能,但是我认为应该立即修复。
博佐

嘿,如果我的Entity已连接并且我有懒惰的东西,SerializationUtils是否检查数据库的懒惰属性?因为这是我想要的,但不是!
Cosmin Cosmin

如果您有一个活跃的会话-是的。
2011年

@Bozho所以,您的意思是,如果Bean中的所有对象都在实现可序列化的功能,那么org.apache.commons.beanutils.BeanUtils.cloneBean(obj)会进行深层复制吗?
2012年

36

约书亚·布洛赫(Joshua Bloch)的书整整有一章,题为“项目10:明智地覆盖克隆”,其中他解释了为什么在大多数情况下覆盖克隆是一个坏主意,因为针对它的Java规范会产生许多问题。

他提供了几种选择:

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

         public static Yum newInstance(Yum yum);
  • 使用复制构造函数:

         public Yum(Yum yum);

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


1
同意 在我的项目中,我定义了一个Copyable包含getCopy()方法的接口。只需手动使用原型模式。
gpampara

好吧,我并不是在问可克隆的接口,而是在问如何执行深层的克隆/复制操作。使用构造函数或工厂,您仍然需要从源中创建新实例。
纪尧姆

@Guillaume我认为您在使用“深克隆/复制”一词时需要小心。用Java克隆和复制并不意味着同一件事。Java规范对此有更多的话要说。。。我想您想从我的发现中得到更深的介绍。
LeWoody

好的Java规范对于克隆是什么是准确的...但是我们也可以用更普遍的含义来谈论克隆...例如,由bohzo推荐的库之一被命名为'Java Deep Cloning Library'...
纪尧姆

2
@LWoodyiii这个newInstance()方法和Yum构造函数会做深拷贝还是浅拷贝?
极客


5

在内存中使用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));

3

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

在大多数情况下,基于反射的gson都可以使用,除了transient不会复制字段和带有cause的循环引用的对象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);

}

2

要看。

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

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


1
反射不是防弹的:在某些情况下,您的克隆对象可能引用了您的源……如果源发生更改,则克隆也将发生更改!
纪尧姆

1

我建议DIY方式,结合良好的hashCode()和equals()方法,应该在单元测试中易于证明。


好吧,在创建这样的虚拟代码时,懒惰使我受益匪浅。但这看起来更明智……
Guillaume

2
抱歉,DIY是要走的路ONLY,如果没有其他的解决方案适合于you..which几乎从未
Bozho

1

我建议重写Object.clone(),先调用super.clone(),然后在要深度复制的所有引用上调用ref = ref.clone()。或多或少自己动手做,但是需要更少的编码。


2
那是(破碎的)克隆方法的许多问题之一:在类层次结构中,您总是必须调用super.clone(),而这很容易被遗忘,这就是为什么我更喜欢使用复制构造函数的原因。
helpermethod 2010年

0

对于深度克隆,可以像这样在每个要克隆的类上实现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);

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.