实现深度对象复制函数有点困难。您采取什么步骤来确保原始对象和克隆对象没有共享引用?


当前回答

一些人提到了使用或重写Object.clone()。不要这样做。Object.clone()存在一些主要问题,在大多数情况下不鼓励使用它。请参见Joshua Bloch的“Effective Java”中的第11项,以获得完整的答案。我相信您可以安全地在基本类型数组上使用Object.clone(),但除此之外,您还需要明智地正确使用和重写clone。

依赖于序列化(XML或其他方式)的方案是拙劣的。

这个问题没有简单的答案。如果你想要深度复制一个对象,你必须遍历对象图,并通过对象的复制构造函数或静态工厂方法显式复制每个子对象,然后再深度复制子对象。不需要复制不可变对象(例如字符串)。顺便说一句,出于这个原因,您应该支持不可变性。

其他回答

您可以使用具有简单API的库,并使用反射执行相对快速的克隆(应该比序列化方法快)。

Cloner cloner = new Cloner();

MyClass clone = cloner.deepClone(o);
// clone is a deep-clone of o

实现深度复制的一种方法是向每个相关类添加复制构造函数。复制构造函数将'this'的实例作为其单个参数,并从该实例复制所有值。工作量很大,但很简单也很安全。

编辑:请注意,您不需要使用访问器方法来读取字段。您可以直接访问所有字段,因为源实例总是与具有复制构造函数的实例具有相同的类型。显而易见,但可能会被忽视。

例子:

public class Order {

    private long number;

    public Order() {
    }

    /**
     * Copy constructor
     */
    public Order(Order source) {
        number = source.number;
    }
}


public class Customer {

    private String name;
    private List<Order> orders = new ArrayList<Order>();

    public Customer() {
    }

    /**
     * Copy constructor
     */
    public Customer(Customer source) {
        name = source.name;
        for (Order sourceOrder : source.orders) {
            orders.add(new Order(sourceOrder));
        }
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

编辑:注意,复制构造函数不考虑继承。例如:如果您将OnlineOrder (Order的子类)传递给一个复制构造函数,则将在副本中创建一个常规的Order实例,除非您显式地解决这个问题。可以使用反射在实参的运行时类型中查找复制构造函数。但是我建议不要走这条路,如果需要以一般的方式讨论继承,就寻找另一种解决方案。

这是一个关于如何深度克隆任何对象的简单示例: 首先实现serializable

public class CSVTable implements Serializable{
    Table<Integer, Integer, String> table; 
    public CSVTable() {
        this.table = HashBasedTable.create();
    }
    
    public CSVTable deepClone() {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(this);

            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            return (CSVTable) ois.readObject();
        } catch (IOException e) {
            return null;
        } catch (ClassNotFoundException e) {
            return null;
        }
    }

}

然后

CSVTable table = new CSVTable();
CSVTable tempTable = table.deepClone();

才能得到克隆体。

一个安全的方法是序列化对象,然后反序列化。这确保了所有内容都是全新的参考。

这里有一篇关于如何有效地做到这一点的文章。

注意:类可以重写序列化,这样就不会创建新的实例,例如单例。当然,如果你的类不是序列化的,这也行不通。

您可以使用序列化进行深度复制,而无需创建文件。

您希望深度复制的对象需要实现可序列化。如果类不是final或不能修改,则扩展类并实现serializable。

将你的类转换为字节流:

ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(object);
oos.flush();
oos.close();
bos.close();
byte[] byteData = bos.toByteArray();

从字节流中恢复类:

ByteArrayInputStream bais = new ByteArrayInputStream(byteData);
Object object = new ObjectInputStream(bais).readObject();