对象数组的深拷贝

Posted

技术标签:

【中文标题】对象数组的深拷贝【英文标题】:Deep copy of an object array 【发布时间】:2011-04-26 05:21:06 【问题描述】:

我想使用构造函数制作对象数组的深层副本。

public class PositionList 
    private Position[] data = new Position[0];

public PositionList(PositionList other, boolean deepCopy) 
        if (deepCopy)
            size=other.getSize();
            data=new Position[other.data.length];
            for (int i=0;i<data.length;i++)
            data[i]=other.data[i];
            

但是,由于某种原因,我上面的内容不起作用。我有我运行的自动化测试,但它没有通过这些测试。所以这里有一个错误,我不确定它是什么。

【问题讨论】:

【参考方案1】:

当你说:

data[i]=other.data[i];

您只是在复制一个引用列表(假设这是一个对象数组)。如果要进行深拷贝,则需要使用new为数组中的每个对象创建一个新实例。

【讨论】:

但是我应该在哪里使用 new 呢?在循环中? 是的。在每次迭代中,创建一个new 对象并将其添加到data 好吧,我很困惑。那么我会为上面的代码删除data=new Position[other.data.length]; 并以某种方式将其添加到循环中吗?【参考方案2】:

而不是说:

data[i]=other.data[i]

您需要为 Position 创建一个复制构造函数(换句话说,一个 Position 的构造函数接受另一个 Position 并复制其中的原始数据)并说 data[i]=new Position(other.data[i]);

基本上你的“深拷贝”构造函数PositionList是一个拷贝构造函数,虽然拷贝构造函数确实倾向于表示一个深拷贝,所以deepCopy参数是不必要的。

【讨论】:

-1 克隆生成浅拷贝而不是深拷贝。它最终可以适用于基元和不可变对象,但不要将其与深层副本混淆。 @bwawok:没错,我假设Position 是一个POJO,因此.clone() 可以工作。 没有克隆怎么办? @fprime:然后使用复制构造函数,正如我的替代解决方案所说。也就是说,我真的应该把“复制构造函数”解决方案放在第一位,因为.clone() 确实有缺点。【参考方案3】:

您实施的是副本。要实现深度副本,您必须 改变

data[i] = other.data[i];

other.data[i]副本 分配给data[i]。你如何做到这一点取决于Position 类。可能的替代方案是:

复制构造函数:

data[i] = new Position(other.data[i]);

工厂方法:

data[i] = createPosition(other.data[i]);

克隆:

data[i] = (Position) other.data[i].clone();

注意事项:

    以上假设复制构造函数、工厂方法和克隆方法分别实现了“正确”的复制,具体取决于 Position 类;见下文。 clone 方法只有在Position 明确支持的情况下才有效,这通常被认为是一种较差的解决方案。此外,您需要注意clone 的本机实现(即Object.clone() 方法)会进行浅拷贝1

事实上,在 Java 中实现深度复制的一般问题是复杂的。在Position 类的情况下,人们会假设属性都是原始类型(例如整数或双精度数),因此深复制与浅复制没有实际意义。但是如果有引用属性,那么您必须依靠复制构造函数/工厂方法/克隆方法来执行您需要的那种复制。在每种情况下都需要对其进行编程。而在一般情况下(您必须处理循环),这很困难,并且需要每个类实现特殊方法。

还有另一种潜在方法可以复制对象数组。如果数组中的对象是serializable,那么你可以通过使用ObjectOutputStreamObjectInputStream 序列化来复制它们,然后对数组进行反序列化。然而:

这很贵, 仅当对象是(可传递的)可序列化的并且 任何transient 字段的值都不会被复制。

不推荐通过序列化复制。最好支持克隆或者其他方法。

总而言之,Java 中最好避免深度复制。

最后,回答你关于Position 类复制构造函数的问题,我希望它是这样的:

public class Position 
    private int x;
    private int y;
    ...
    public Position(Position other) 
        this.x = other.x;
        this.y = other.y;
    
    ...

正如@Turtle 所说,没有任何魔法。您实现了一个构造函数(手动),它通过从现有实例复制来初始化其状态。


1 - 指定 clone() 的 Object 实现执行浅拷贝,但这可能会被覆盖。 clone 的 javadoc 指定“合同”如下:

“创建并返回此对象的副本。“副本”的确切含义可能取决于对象的类。一般意图是,对于任何对象 x,表达式:@ 987654340@ 为真,表达式:x.clone().getClass() == x.getClass() 为真,但这些不是绝对要求。虽然通常情况下:x.clone().equals(x) 为真,但这不是绝对要求。” em>

“合同”中没有提到深拷贝和浅拷贝。所以如果你打算在这个上下文中使用clone,你需要知道clone方法的实际类的行为。

【讨论】:

复制构造函数是我需要的。你上面的那个工作得很好,但我不明白。所以我们正在创建一个新位置,但是我们提供给它的参数是什么,other.data[i]?你能描述一下它是如何工作的吗? @fprime:这不是魔法。有人需要为 Position 类编写一个构造函数,该构造函数将该类的实例作为其唯一参数并返回一个深层副本。 不一定 clone() 做深拷贝:例如,返回这个 ArrayList 实例的浅拷贝。 (元素本身不会被复制。) 关于第三个选项,clone() - 确保 Point 实现 Cloneable 1) No. 2) 鉴于没有复制Object 的通用方法,因此不可能“深度复制”任意Object[]【参考方案4】:

这应该是一个“深”的副本

int [] numbers =  2, 3, 4, 5;

int [] numbersClone = (int[])numbers.clone();

【讨论】:

这适用于基元数组(如整数),但问题是关于对象数组。【参考方案5】:

这是我使用的一个函数:

function copy(arr) 
  return arr
    .map(x => Object
      .keys(x)
      .reduce((acc, y) => 
        acc[y] = x[y]
        return acc
      , ))

它仅适用于具有单层对象的数组。

【讨论】:

以上是关于对象数组的深拷贝的主要内容,如果未能解决你的问题,请参考以下文章

对象或数组 或数组对象的深拷贝

拷贝数组和对象,深拷贝,浅拷贝

对象数组实例的深拷贝,Java

6javascript中对象和数组的深拷贝

javascript 数组以及对象的深拷贝(复制数组或复制对象)的方法

数组和对象的深拷贝