6

假设我有一个整数数组,“orig”

我想浅拷贝它,所以我不能这样做:

int[] shallow = orig;

我的教授说,对于原语,浅拷贝和深拷贝本质上是相同的,因为我们必须复制数组的每个索引。但是设置整个数组等于另一个数组做同样的事情,对吧?

我对对象数组有类似的问题

这是我的意识形态

Book[] objArr2 = objArr1;

但有人告诉我,我必须复制每个数组索引,比如

//for loop
objArr2[i] = objArr1[i];

对于浅拷贝,将数组与另一个数组相等并单独复制每个数组索引之间真的有什么区别吗?(我知道深度意味着您必须创建全新的对象)

4

4 回答 4

17

我想浅拷贝它,所以我不能这样做:

int[] 浅= orig;

那不是真正的浅拷贝。副本是与原始项目相似但不是原始项目的离散实体。在您的示例中,您实际上拥有的是两个指向同一个对象的引用。创建副本时,您应该有两个结果对象:原始对象和副本。

在这里,您所做的任何修改shallow都会发生,orig因为它们都指向同一个对象。

当您正在比较的对象具有对其中其他对象的引用时,“浅薄”就会发挥作用。例如,如果您有一个整数数组并创建了一个副本,那么您现在有两个数组都包含相同的整数值:

Original Array

[0]
[1]
[2]
[3]

After copying:

[0] <--- Original  [0]
[1]                [1]
[3]                [2]
[4]      Copy ---> [3]

但是,如果你有一个由对象组成的数组(比如说objArr1and objArr2)呢?当你做一个浅拷贝时,你现在有两个新的数组对象,但是两个数组之间的每个对应条目都指向同一个对象(因为对象本身没有被复制;只是引用有)。

Original Array:

[0:]----> [object 0]
[1:]----> [object 1]
[2:]----> [object 2]
[3:]----> [object 3]

复制后(注意相应的位置如何指向相同的实例):

Original -> [0:]----> [object 0] <----[:0] <- Copy
            [1:]----> [object 1] <----[:1]
            [2:]----> [object 2] <----[:2]
            [3:]----> [object 3] <----[:3]

现在,如果您objArr1通过替换条目或删除条目进行修改,同样的事情不会发生在objArr2. 但是,如果您在 处修改对象objArr1[0],也会反映在 中objArr2[0],因为这些位置指向同一个对象。所以在这种情况下,即使容器对象本身是不同的,它们所包含的是对同一对象的引用。

当您进行深度复制时,您将创建两个新数组,其中每个对应位置指向不同的实例。所以基本上你一直在复制对象。

我的教授说,对于原语,浅拷贝和深拷贝本质上是相同的,因为我们必须复制数组的每个索引。

要做出的重要区别是,当您复制一个基元数组时,您正在完全复制这些值。每次你得到一个的原语。然而,当你有一个对象数组时,你真正拥有的是一个对对象的引用数组。因此,当您创建副本时,您所做的就是创建一个数组,其中包含原始数组中引用的副本。但是,这些引用的新副本仍然指向相同的对应对象。这就是所谓的浅拷贝。如果您对数组进行深度复制,那么每个单独位置所引用的对象也将被复制。所以你会看到这样的东西:

Original -> [0:]----> [object 0] Copy -> [0:]----> [copy of object 0]
            [1:]----> [object 1]         [1:]----> [copy of object 1]
            [2:]----> [object 2]         [2:]----> [copy of object 2]
            [3:]----> [object 3]         [3:]----> [copy of object 3]

但是设置整个数组等于另一个数组做同样的事情,对吧?

不,不是的。您在这里所做的只是创建对现有数组的新引用:

arr1 -> [0, 1, 2, 3, 4]

现在假设你做到了arr2 = arr1。你所拥有的是:

arr1 -> [0, 1, 2, 3, 4] <- arr2

所以这里arr1, 和arr2都指向同一个数组。因此,您使用的任何修改arr1都将在您访问数组时反映出来,arr2因为您正在查看同一个数组。制作副本时不会发生这种情况。

于 2012-10-31T20:34:32.517 回答
1

这里的问题是您需要将数组视为对象。您存储的objArr1是一个引用数组开头的内存地址。因此,例如,如果objArr1数组存储在地址 0x1234578 则实际的值objArr1是 0x1234578 当你说

objArr2 = objArr1;

你说的值objArr2应该等于0x1234578。现在,如果您更改存储在 0x1234578 的数组的元素之一,那么无论您引用它objArr1[1]还是使用objArr2[1]该值都将是相同的。如果您尝试更改其中一个,情况也是如此:您对其中一个所做的任何事情都会反映在另一个上,因为它们指向同一个地方。例如下面的代码将是真的

objArr2 = objArr1;
objArr2[0] = 5;
objArr1[0] = 6;
System.out.println(objArr2[0]); //prints "6"

有时这种行为很有用,但不会产生副本。

于 2012-10-31T20:33:17.053 回答
1

对于基元

考虑以下示例:

public class Example {

    //this class just uses the reference as you suggest
    public static class ArrayEater {

        private final int[] food;

        public ArrayEater(final int[] food) {
            this.food = food; // references same array, does not crate copy
        }

        public void eat() {
            for (int index = 0; index < food.length; index++) {
                final int bite = food[index];
                if (bite == 0) {
                    System.out.println("No food at position " + index);
                } else {
                    System.out.println("Eating " + bite + " from position " + index);
                }
                food[index] = 0;
            }
        }
    }

    //this class makes an actual copy
    public static class ArrayCopyThenEatEater {

        private final int[] food;

        public ArrayCopyThenEatEater(final int[] food) {
            this.food = new int[food.length]; // creates new array
            for (int index = 0; index < food.length; index++) { //copies over the values
                this.food[index] = food[index];
            }
        }

        public void eat() {
            for (int index = 0; index < food.length; index++) {
                final int bite = food[index];
                if (bite == 0) {
                    System.out.println("No food at position " + index);
                } else {
                    System.out.println("Eating " + bite + " from position " + index);
                }
                food[index] = 0;
            }
        }
    }

    public static void main(String[] args) {

        int[] originalArray = {1,3,6,9};
        ArrayEater eater = new ArrayEater(originalArray);
        eater.eat(); 
        eater.eat(); 
        for (int index = 0; index < originalArray.length; index++) {
            System.out.println("Original array has value of " + originalArray[index] + " at position " + index);
        }

        originalArray = new int[]{1,3,6,9};
        ArrayCopyThenEatEater copyEater = new ArrayCopyThenEatEater(originalArray);
        copyEater.eat(); 
        copyEater.eat(); 
        for (int index = 0; index < originalArray.length; index++) {
            System.out.println("Original array has value of " + originalArray[index] + " at position " + index);
        }
    }

}

如果你看一下 main 方法,你会看到 anoriginalArray被创建并传递给了两个“食者”。其中一个食者只是按照您的建议引用 originalArray,另一个食者创建了一个实际的副本(就像您的教授试图说的那样,对于原始人来说,这个副本既浅又深)。

运行它会创建以下输出:

Eating 1 from position 0
Eating 3 from position 1
Eating 6 from position 2
Eating 9 from position 3
No food at position 0
No food at position 1
No food at position 2
No food at position 3
Original array has value of 0 at position 0  <-- here we see that the eater ate the original!!
Original array has value of 0 at position 1
Original array has value of 0 at position 2
Original array has value of 0 at position 3
Eating 1 from position 0
Eating 3 from position 1
Eating 6 from position 2
Eating 9 from position 3
No food at position 0
No food at position 1
No food at position 2
No food at position 3
Original array has value of 1 at position 0 <-- here we see that the eater did not eat the original!!
Original array has value of 3 at position 1
Original array has value of 6 at position 2
Original array has value of 9 at position 3

查看上面的内容,您可以看到您的方法导致原始数组的内容被覆盖,而您的教授建议的复制方法不会更改原始数组。


对于非原始人

在这里,我们有一个Food已经被吃掉或者没有被吃掉的类。我们有 3 个吃者,参考副本、浅副本和深副本各一个:

public class Example {

    public static class Food implements Cloneable {
        private boolean eaten = false;
        public void eat() {
            eaten = true;
        }
        public boolean isEaten() {
            return eaten;
        }
        public Food clone() {
            try {
                return (Food) super.clone();
            } catch (CloneNotSupportedException e) {
                return null; //we won't get here
            }
        }
    }

    public static class ReferenceEater {

        private final Food[] food;

        public ReferenceEater(final Food[] food) {
            this.food = food; // references same array, does not crate copy
        }

        public void eat() {
            for (int index = 0; index < food.length; index++) {
                final Food bite = food[index];
                if (bite.isEaten()) {
                    System.out.println("No food at position " + index);
                } else {
                    System.out.println("Eating from position " + index);
                    bite.eat();
                }
            }
        }
    }

    public static class ShallowEater {

        private final Food[] food;

        public ShallowEater(final Food[] food) {
            this.food = new Food[food.length]; // creates new array
            for (int index = 0; index < food.length; index++) {
                this.food[index] = food[index]; //shallow copy still references same elements!
            }
        }

        public void eat() {
            for (int index = 0; index < food.length; index++) {
                final Food bite = food[index];
                if (bite.isEaten()) {
                    System.out.println("No food at position " + index);
                } else {
                    System.out.println("Eating from position " + index);
                    bite.eat();
                }
            }
        }
    }

    public static class DeepEater {

        private final Food[] food;

        public DeepEater(final Food[] food) {
            this.food = new Food[food.length]; // creates new array
            for (int index = 0; index < food.length; index++) {
                this.food[index] = food[index].clone(); //deep copy also copies the elements!
            }
        }

        public void eat() {
            for (int index = 0; index < food.length; index++) {
                final Food bite = food[index];
                if (bite.isEaten()) {
                    System.out.println("No food at position " + index);
                } else {
                    System.out.println("Eating from position " + index);
                    bite.eat();
                }
            }
        }
    }

    public static void main(String[] args) {

        Food[] originalArray = {new Food(), new Food(), new Food()};
        ReferenceEater referenceEater = new ReferenceEater(originalArray);
        referenceEater.eat(); 
        referenceEater.eat(); 
        for (int index = 0; index < originalArray.length; index++) {
            System.out.println("Food at position " + index + " has been eaten?  " + originalArray[index].isEaten());
        }

        originalArray = new Food[]{new Food(), new Food(), new Food()};
        ShallowEater shallowEater = new ShallowEater(originalArray);
        shallowEater.eat(); 
        shallowEater.eat(); 
        for (int index = 0; index < originalArray.length; index++) {
            System.out.println("Food at position " + index + " has been eaten?  " + originalArray[index].isEaten());
        }

        originalArray = new Food[]{new Food(), new Food(), new Food()};
        DeepEater deepEater = new DeepEater(originalArray);
        deepEater.eat(); 
        deepEater.eat(); 
        for (int index = 0; index < originalArray.length; index++) {
            System.out.println("Food at position " + index + " has been eaten?  " + originalArray[index].isEaten());
        }
    }

}

请注意,深拷贝还复制了数组的各个元素。

现在看看输出:

Eating from position 0
Eating from position 1
Eating from position 2
No food at position 0
No food at position 1
No food at position 2
Food at position 0 has been eaten?  true
Food at position 1 has been eaten?  true
Food at position 2 has been eaten?  true
Eating from position 0
Eating from position 1
Eating from position 2
No food at position 0
No food at position 1
No food at position 2
Food at position 0 has been eaten?  true
Food at position 1 has been eaten?  true
Food at position 2 has been eaten?  true
Eating from position 0
Eating from position 1
Eating from position 2
No food at position 0
No food at position 1
No food at position 2
Food at position 0 has been eaten?  false
Food at position 1 has been eaten?  false
Food at position 2 has been eaten?  false

在这里,我们看到,与原语一样,引用副本导致原件再次被吃掉。但是看一下浅拷贝,我们得到的和上面的图元的深/浅拷贝是一样的,这次食物已经在原来的状态下被吃掉了(与原语不同!)。这是因为虽然我们创建了一个新数组,但我们传入了对相同 Food 实例的引用。最后,通过深度副本,我们看到原始数组食物项没有被吃掉,因为食者吃了这些食物的克隆。

于 2012-10-31T21:14:18.750 回答
0

您的示例使用数组,所以我将坚持使用它们作为示例。数组实际上只是“分区”的内存块(基于类型)。类似 int[] a 的值是该数组的起始内存地址。当您执行 int[] a = someOtherArray 时,您正在为其分配另一个内存位置的地址,而不是它们存储的值。因此,您必须逐个元素地分配存储在每个位置的值。

于 2012-10-31T20:28:15.273 回答