在Java中复制一个对象

21

我在Java中有一个需要复制的对象,需要创建一个副本并对其进行一些测试,而不改变原始对象本身。

我认为我需要使用clone()方法,但这个方法是受保护的。在网上做了一些研究后,我发现可以通过在我的类中重写一个公共方法来覆盖它。但我找不到关于如何做到这一点的解释。怎么实现?

此外,这是实现我所需的最佳方法吗?


这是仅用于测试目的(单元测试),还是您需要在实际生产代码中使用此功能? - crunchdog
7个回答

30

使用复制构造函数是另一种选项(来自Java Practices):

public final class Galaxy {

    public Galaxy (double aMass, String aName) {
        fMass = aMass;
        fName = aName;
    }

    /**
    * Copy constructor.
    */
    public Galaxy(Galaxy aGalaxy) {
        this(aGalaxy.getMass(), aGalaxy.getName());
        //no defensive copies are created here, since 
        //there are no mutable object fields (String is immutable)
    }

    /**
    * Alternative style for a copy constructor, using a static newInstance
    * method.
    */
    public static Galaxy newInstance(Galaxy aGalaxy) {
        return new Galaxy(aGalaxy.getMass(), aGalaxy.getName());
    }

    public double getMass() {
        return fMass;
    }

    /**
    * This is the only method which changes the state of a Galaxy
    * object. If this method were removed, then a copy constructor
    * would not be provided either, since immutable objects do not
    * need a copy constructor.
    */
    public void setMass( double aMass ){
        fMass = aMass;
    }

    public String getName() {
        return fName;
    }

    // PRIVATE /////
    private double fMass;
    private final String fName;

    /**
    * Test harness.
    */
    public static void main (String... aArguments){
        Galaxy m101 = new Galaxy(15.0, "M101");

        Galaxy m101CopyOne = new Galaxy(m101);
        m101CopyOne.setMass(25.0);
        System.out.println("M101 mass: " + m101.getMass());
        System.out.println("M101Copy mass: " + m101CopyOne.getMass());

        Galaxy m101CopyTwo = Galaxy.newInstance(m101);
        m101CopyTwo.setMass(35.0);
        System.out.println("M101 mass: " + m101.getMass());
        System.out.println("M101CopyTwo mass: " + m101CopyTwo.getMass());
    }
} 

4
在复制构造函数中,我不会使用getter。首先,您不需要它们,因为您始终可以访问实例变量。其次,如果您将这个做法自己实践,可能会通过创建过多的getter降低封装性。最后,这可能存在错误。想象一下,如果您调整getMass()中的值,那么您将复制已调整的值到新对象的实例变量中,如果在新对象上调用getMass,则会再次调整该值。 - Norbert Hartl
@Mr_and_Mrs_D,你说得对 - 我第一次阅读时没有注意到那个。Norbert的观点仍然成立。 - Bob Cross

21

有两种流行的方法。其中一种是提供如下所示的clone方法。

public class C implements Cloneable {
    @Override public C clone() {
        try {
            final C result = (C) super.clone();
            // copy fields that need to be copied here!
            return result;
        } catch (final CloneNotSupportedException ex) {
            throw new AssertionError();
        }
}

注意 "copy fields ... here!" 这部分内容。最初的 result 只是一个浅拷贝,这意味着如果存在对对象的引用,原始对象和 result 将共享同一个对象。例如,如果 C 包含 private int[] data,则您可能需要进行复制。

...
final C result = (C) super.clone();
result.data = data.clone();
return result;
...
注意,您不需要复制原始字段,因为它们的内容已经被复制,也不需要复制不可变对象,因为它们无论如何都不能更改。
第二种方法是提供一个复制构造函数。
public class C {
    public C(final C c) {
        // initialize this with c
    }
}
或者是一个复制工厂。
public class C {
    public static C newInstance(final C c) {
        return new C(c);
    }

    private C(final C c) {
        // initialize this with c
    }
}

两种方法都有各自的特点。clone很好,因为它是一个方法,所以你不必知道确切的类型。最终,你应该总是得到一个“完美”的副本。复制构造函数也很好,因为调用者有机会决定,就像Java集合中所看到的那样。

final List c = ... 
// Got c from somewhere else, could be anything.
// Maybe too slow for what we're trying to do?

final List myC = new ArrayList(c);
// myC is an ArrayList, with known properties

我建议选择其中一种方法,选择适合您的那个。

对于其他方法,如反射复制或立即序列化/反序列化,我只会在单元测试中使用。 对我而言,它们对于生产代码来说感觉不太合适,主要是因为性能问题。


14

几种选择:

  • 您可以为您的对象实现Cloneable接口,并将clone()方法设置为公共方法。在这里查看完整说明:http://www.cafeaulait.org/course/week4/46.html
    但是这会产生浅层复制,可能不是您想要的。
  • 您可以对对象进行序列化和反序列化。您需要为对象及其所有字段实现Serializable接口。
  • 您可以使用XStream通过XML执行序列化 - 这里不需要实现任何内容。

9

对于测试代码,序列化可能是最安全的选择,特别是如果对象已经是可序列化的,则可以尝试使用Apache Commons SerializationUtils进行实现。


3

您可以使用org.apache.commons.lang3.SerializationUtils类进行对象克隆, - 类应实现Serializable接口。

  • ClassName copyobject = SerializationUtils.clone(classobjectTocopy)

SerializationUtils.clone也支持在Google App Engine实例上使用


2

Joshua Bloch在克隆方面有一些有趣的想法。根据对象的大小/构造方式,我会为对象添加一个复制构造函数,或使用上述解决方案之一进行序列化/反序列化。


2

在Java中,有多种方法可以复制对象(浅复制或深复制)。
这个答案会对你有所帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接