Arrays.copyOf生成浅拷贝还是深拷贝?

21

关于 Arrays.copyOf 是否会产生深拷贝还存在着很多的混淆和不同的观点,其中包括 [1] 和其他来源。

这个测试表明拷贝是深拷贝:

String[] sourceArray = new String[] { "Foo" };
String[] targetArray = java.util.Arrays.copyOf( sourceArray, 1 );

sourceArray[0] = "Bar";

assertThat( targetArray[0] ).isEqualTo( "Foo" ); // passes

这个测试表明复制是浅层的:

String[][] sourceArray = new String[][] { new String[] { "Foo" } };
String[][] targetArray = java.util.Arrays.copyOf( sourceArray, 1 );

sourceArray[0][0] = "Bar";

assertThat( targetArray[0][0] ).isEqualTo( "Foo" ); // fails

解决方案是否只是制作顶层维度的深复制,而其他维度是浅复制?真相是什么?

[1] 如何在Java中进行二维数组的深层复制?


可能只是字符串内部化的另一个实例。 - Matthias
@Matthias:我不这么认为。由于“Foo”是一个字面量,它将被存储在常量池中;测试假设如此。如果这个假设是正确的,那么测试就是在调查目标元素是否已经被相应源元素的= "Bar"修改了。 - ToolmakerSteve
我没有看到测试对字符串是否内部化做出任何假设。我没有看到任何身份测试,只看到相等性测试。结果对于浅层复制和深层复制是相同的,因为相等性测试不能区分浅层复制和深层复制。需要使用身份测试来区分浅层复制和深层复制。 - Christian Hujer
6个回答

29

它生成一个浅拷贝,即一个包含“旧”引用(指向相同对象的引用,没有被复制)的数组。

特别地,如果你有嵌套的数组,它们将不会被复制。你只会得到一个新数组,其“顶层”指向和原来相同的“次级”数组。对于那些嵌套数组内部的任何更改都会在复制和原始数组中反映出来。

这个测试表明复制是深层次的:

不是的。当你把一个新对象赋值给“原始”数组时,这并不影响复制品。毕竟,它是一份拷贝。

这与下面的情况相同:

String x = "foo";
String y = x;
x = "bar";

assertEquals(y, "foo");

这里没有“深拷贝”。


1D int[] 数组将会进行深度复制。 - 01000001

7

Java Doc中可以看到:

......两个数组将包含相同的值。

因此,在包含引用的数组的情况下,只复制引用而不是实际对象。这意味着进行浅层复制。


4

'浅层复制'或'深层复制' - 这是一个我看到没有人定义清楚的问题 - 方法Arrays.copyOf(..)在实践中确实会产生源数组的副本,该副本不受源数组更改的影响。

以int数组为例,看下面这个简单的例子:

import java.util.Arrays;

public class DeepCopyTest
{

    public static void main(String[] args)
    {
       int[] source = { 1, 2, 3, 4, 5, 6};
       int[] target = new int[source.length];
       // Copy target from source via Arrays.copyOf(..) method :
       target = Arrays.copyOf(source, 6);
       // Check for equality :
       System.out.println("Source1 : " + Arrays.toString(source));
       System.out.println("Target1 : " + Arrays.toString(target));
       // Increment all entries in source array :
       for(int i = 0; i < source.length; i++)
       {
          source[i] = source[i] +1;
       }
       // See if target is affected :
       System.out.println("Source2 : " + Arrays.toString(source));
       System.out.println("Target2 : " + Arrays.toString(target));

    }

}

// OUTPUT
// ------
Source1 : [1, 2, 3, 4, 5, 6]
Target1 : [1, 2, 3, 4, 5, 6]
Source2 : [2, 3, 4, 5, 6, 7]
Target2 : [1, 2, 3, 4, 5, 6]

实际上,当人们寻求一个数组的“深拷贝”时,他们只是希望得到一个与原始数组更改无关的东西。

而这个 Arrays.copyOf(..) 方法确实可以给他们这个。

除了基本数据类型数组外,String 对象数组也像上面的例子一样运行,输出类似于:

Source1 : [a, b, c, d, e, f]
Target1 : [a, b, c, d, e, f]
Source2 : [a1, b1, c1, d1, e1, f1]
Target2 : [a, b, c, d, e, f]

当初始源数组条目被“1”连接时。

对于对象数组,它也“可以工作”,因为当后者被重新分配时,目标不再与源相关联。但是,在复制并修改source [0]之后,查看两个数组的第一个元素的输出会揭示全部真相:

Source1 : java.lang.Object@1db9742
Target1 : java.lang.Object@1db9742
Source2 : java.lang.Object@106d69c
Target2 : java.lang.Object@1db9742

在复制原始源数组后,目标元素只是指向其源对应项当前持有的任何值。对于目标[0]来说,它指向内存地址1db9742中的内容,这也是保存源[0]的相同内存地址......

当重新分配源[0]之后,我们会看到源和目标之间出现解绑的原因是赋值语句......

source[0] = new Object();

简单地说,使用 Arrays.copyOf(..) 方法可以将源数组中的内存引用更改为指向新对象的位置,从而实现复制。虽然在许多情况下它可以给程序员带来与深拷贝相同的好处,但它并不是真正意义上的深拷贝。

对于基本数据类型的数组,Arrays.copyOf(..) 方法无法复制引用,因为这些引用不适用于基本数据类型。它只会将源元素值复制到目标元素中。同样,我们可以通过这种方法获得类似于深拷贝的效果,但所需代码远少于深拷贝。

因此,Arrays.copyOf(..) 方法是一种廉价的深拷贝方式,适用于基本数据类型和一维对象数组。但对于任何更复杂的数据数组,它都会暴露问题。

也许它应该被称为半深拷贝。


1

它创建浅拷贝,因为Java使用按值传递参数,所有变量的副本都可在克隆对象中使用,但对于引用类型变量,会创建地址的副本并指向由原始数组引用的同一对象,因此当修改复制的对象时,原始数组中的原始对象也会被更新。请参见下面的代码。

import java.util.*;
import java.lang.*;
import java.io.*;

/* Name of the class has to be "Main" only if the class is public. */
class ArraysCopyOfDemo
{
    public static void main (String[] args) throws java.lang.Exception
    {
        Object[] originalArray= new Object[1];
        Employee e1= new Employee("Salman","Khan");
        originalArray[0]=e1;
        System.out.println("Original Array content printed ");
        printArray(originalArray);

      Object[] copiedArray=originalArray.clone();
        System.out.println("Copied Array content printed ");
        printArray(copiedArray);
        System.out.println("Copied Array content modified ");
        Employee CopiedEmp1= (Employee)copiedArray[0];
        CopiedEmp1.setFirstname("Amir");
        System.out.println("Copied Array content printed ");
        printArray(copiedArray);
        System.out.println("Original Array content printed to verify shallow copy or deep copy");
        printArray(originalArray);
    }
    private static void printArray(Object[] arrays ){
        for(Object emp:arrays){
            System.out.print(((Employee)emp).getFirstname() + " ");
            System.out.print(((Employee)emp).getLastname());
            System.out.println();
        }
    }
}
class Employee implements Cloneable{
    private String firstname;
    private String lastname;
    public Employee(String firstname,String lastname){
        this.firstname=firstname;
        this.lastname=lastname;
    }
    public String getFirstname(){
       return firstname;
    }
    public String getLastname(){
        return lastname;
    }
    public void setFirstname(String firstname){
        this.firstname=firstname;
    }
     public void setLirstname(String lastname){
         this.lastname=lastname;
    }

}

O/p
Original Array content printed 
Salman Khan
Copied Array content printed 
Salman Khan
Copied Array content modified 
Copied Array content printed 
Amir Khan
Original Array content printed to verify shallow copy or deep copy
Amir Khan

1

这是一个使用=运算符进行简单浅拷贝的方法。

如果更改其中任何一个变量所引用的对象,另一个变量仍将包含对旧对象的旧引用,这会导致混淆。

而如果修改对象本身,则两个变量中的对象都会被更改。

    var car1=new car(1);
    var car2=car1;
    car1=new car(2);
    //car1.id=2 ,car2.id=1;

    var car1=new car(1);
    var car2=car1;
    car1.id=2
    //car1.id=2 ,car2.id=2;

-4

这是一个深拷贝。在字符串的情况下,它看起来很浅,因为在底层,字符串是单例的。JVM 为字符串维护了一块内存池,并且只创建每个唯一字符串的一个副本。所以你总是得到对该字符串引用的副本。下面的示例显示了类 Object 的深拷贝。当更改原始数组时,副本不会改变。

public class ArrayTest {

public static void main(String [] args) {
    Object [] objs = new Object[1];
    objs[0] = new Object();
    System.out.println("Original: " + objs[0].toString());

    Object [] copy = java.util.Arrays.copyOf(objs, 1);
    objs[0] = new Object();
    System.out.println("copy, after original reassigned: " +
    copy[0].toString());
    System.out.println("Original, after reassigned: " +
    objs[0].toString());
}

}


1
这不是深拷贝,深拷贝需要复制数组中存储的所有对象本身。Arrays.copyOf并不会这样做;它只是复制引用 - 即浅拷贝。 - flamming_python
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - ToolmakerSteve
很抱歉,您的答案有多处错误。该复制是浅复制。字符串并不总是内部化,只有在特殊情况下才会这样做。例如,在 String foo = "Foo"; String foo2 = new String(foo); 中,foo2 引用了一个新的副本。而且,无法从重写的 Object.equals() 方法中推断出复制是否为深复制,因为这是 Object.equals() 根据定义隐藏的内容。 - Christian Hujer

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