问题
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