Shallow copy for arrays, why can't simply do newArr = oldArr?

ぃ、小莉子 提交于 2021-02-06 09:19:51

问题


Let's say I have an array of integers, "orig"

I want to shallow copy it, so can't I just do this:

int[] shallow = orig;

My professor said that for primitives, shallow and deep copy are essentially the same, in that we have to copy over each index of the array. But setting the whole array equals to another array does the same thing, right?

I have a similar question with object arrays

This was my ideology

Book[] objArr2 = objArr1;

But I was told that I would have to copy each array index over, like

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

For shallow copying is there really any difference between equaling arrays to another, and individually copy each array index? (I understand that deep means you have to create brand new objects)


回答1:


I want to shallow copy it, so can't I just do this:

int[] shallow = orig;

That's not really a shallow copy. A copy is a discrete entity that is similar to the original, but is not the original item. In your example, what you actually have are two references that are pointing to the same object. When you create a copy, you should have two resulting objects: the original and the copy.

Here, anything you do to modify shallow will happen to orig as well since they both point to the same object.

"Shallowness" comes into play when the object you are comparing has references to other objects inside it. For example, if you have an array of integers and you create a copy, you now have two arrays which both contain the same integer values:

Original Array

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

After copying:

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

However, what if you had an array that consists of objects (let's say objArr1 and objArr2)? When you do a shallow copy you now have two new array objects, but each corresponding entry between the two arrays points to the same object (because the objects themselves haven't been copied; just the references have).

Original Array:

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

After copying (notice how the corresponding locations are pointing to the same instances):

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

Now if you modify objArr1 by replacing an entry or deleting an entry, that same thing doesn't happen to objArr2. However if you modify the object at objArr1[0], that is reflected in objArr2[0] as well since those locations point to the same object. So in this case, even though the container objects themselves are distinct, what they contain are references to the same object.

When you do a deep copy, you will two new arrays where each corresponding location points to different instances. So essentially you make copies of objects all the way down.

My professor said that for primitives, shallow and deep copy are essentially the same, in that we have to copy over each index of the array.

The important distinction to make is that when you copy an array of primitives, you are copying the values over exactly. Each time you get a new primitive. However, when you have an array of objects, what you really have is an array of references to objects. So when you create a copy, all you have done is create a new array that has copies of the references in the original array. However, these new copies of the references still point to the same corresponding objects. This is what's known as a shallow copy. If you deep-copied the array, then the objects that each individual location refers to, will have been copied also. So you would see something like this:

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]

But setting the whole array equals to another array does the same thing, right?

No it does not. What you're doing here is simply creating a new reference to an existing array:

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

Now let's say you did arr2 = arr1. What you have is:

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

So here both arr1, and arr2 are pointing to the same array. So any modification you perform using arr1 will be reflected when you access the array using arr2 since you are looking at the same array. This doesn't happen when you make copies.




回答2:


The problem here is that you need to think of an array as an object. What you have stored in objArr1 is a memory address that references the beginning of the array. So, for instance, if the objArr1 array is stored at address 0x1234578 then the value of the objArr1 is effectively 0x1234578 and when you say

objArr2 = objArr1;

You're saying the value of objArr2 should be equal to 0x1234578. Now, if you change element one of the array stored at 0x1234578 then whether you reference it with objArr1[1] or objArr2[1] the value will be the same. And the same is true if you try to change one of them: anything you do to one, will be reflected in the other because they point to the same place. For instance the following code will be true

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

Sometimes that behavior is useful, but that doesn't result in a copy.




回答3:


For Primitives

Consider the following example:

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);
        }
    }

}

If you look at the main method, you will see that an originalArray is created and passed to the two "eaters". One of the eaters just references the originalArray as you suggest doing, the other eater creates an actual copy (like your professor tried saying, this copy is both shallow and deep for a primitive).

Running this creates the following output:

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

Looking at the above, you can see that your approach causes the original array's content to be overwritten, whereas the copy approach suggested by your professor does not change the original array.


For Non-Primitives

Here, we have a class Food that has either been eaten or it has not. We have three eaters, one each for referential copy, shallow copy, and deep copy:

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());
        }
    }

}

Notice that the deep copy also copies the individual elements of the array.

Now look at the output:

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

Here, we see that, like with the primitives, the referential copy caused the original to be eaten once again. But looking at the shallow copy, which we obtain the same as the deep/shallow copy for the primitives above, the food has all been eaten in the original this time (unlike with the primitives!). This is because although we created a new array, we passed in references to the same Food instances. Finally, with the deep copy we see that the original array food items were not eaten, as the eater ate clones of those items.




回答4:


Your examples use arrays, so I will just stick to using them as an example. Arrays are really just "partitioned" memory blocks (based on the type). The value held by something like int[] a, is the starting memory address for that array. When you do int[] a = someOtherArray, you are assigning it the address of another memory location, and not the values they store. Therefore, you have to go element by element and assigning the values stored at each location.



来源:https://stackoverflow.com/questions/13166884/shallow-copy-for-arrays-why-cant-simply-do-newarr-oldarr

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!