Java - Implement deep and shallow copy of an array
Asked Answered
M

3

8

I am trying to understand the concept of shallow vs deep copy in Java. There is a lot of articles and Q&A about this subject, but whenever I try to implement these concepts in a real Java code, everything become unclear to me.

One of the answers on which I base my understanding is in this link, where deep and shallow copying are explained via schemas.

I will show you below my implementation for each case:

  • Shallow copy:

I took for my example the method System.arraycopy() as I read in many articles that it performs a shallow copy (along with the clone method)

public class Test {

    public static void main(String[] args) {
        NameValue[] instance1 = {
                new NameValue("name1", 1),
                new NameValue("name2", 2),
                new NameValue("name3", 3),
        };
        NameValue[] instance2 = new NameValue[instance1.length];

        // Print initial state
        System.out.println("Arrays before shallow copy:");
        System.out.println("Instance 1: " + Arrays.toString(instance1));
        System.out.println("Instance 2: " + Arrays.toString(instance2));

        // Perform shallow copy
        System.arraycopy(instance1, 0, instance2, 0, 3);

        // Change instance 1
        for (int i = 0; i < 3; i++) {
            instance1[i].change();
        }

        // Print final state
        System.out.println("Arrays after shallow copy:");
        System.out.println("Instance 1: " + Arrays.toString(instance1));
        System.out.println("Instance 2: " + Arrays.toString(instance2));
    }

    private static class NameValue {
        private String name;
        private int value;

        public NameValue(String name, int value) {
            super();
            this.name = name;
            this.value = value;
        }

        public void change() {
            this.name = this.name + "-bis";
            this.value = this.value + 1;
        }

        @Override
        public String toString() {
            return this.name + ": " + this.value;
        }
    }
}

The result of the execution of the main methods is as follows:

Arrays before shallow copy:
Instance 1: [name1: 1, name2: 2, name3: 3]
Instance 2: [null, null, null]
Arrays after shallow copy:
Instance 1: [name1-bis: 2, name2-bis: 3, name3-bis: 4]
Instance 2: [name1-bis: 2, name2-bis: 3, name3-bis: 4]

this result is an accordance with the schema of the previous link: Shallow copy

  • Deep copy:

I took for this example the method Arrays.copyOf() as I read in many articles that it performs a deep copy (along with the Arrays.copyOfRange method)

public static void main(String[] args) {
    NameValue[] instance1 = {
            new NameValue("name1", 1),
            new NameValue("name2", 2),
            new NameValue("name3", 3),
    };
    NameValue[] instance2 = new NameValue[instance1.length];

    // Print initial state
    System.out.println("Arrays before deep copy:");
    System.out.println("Instance 1: " + Arrays.toString(instance1));
    System.out.println("Instance 2: " + Arrays.toString(instance2));

    // Perform deep copy
    instance2 = Arrays.copyOf(instance1, 3);

    // Change instance 1
    for (int i = 0; i < 3; i++) {
        instance2[i].change();
    }

    // Print final state
    System.out.println("Arrays after deep copy:");
    System.out.println("Instance 1: " + Arrays.toString(instance1));
    System.out.println("Instance 2: " + Arrays.toString(instance2));
}

which displays:

Arrays before deep copy:
Instance 1: [name1: 1, name2: 2, name3: 3]
Instance 2: [null, null, null]
Arrays after deep copy:
Instance 1: [name1-bis: 2, name2-bis: 3, name3-bis: 4]
Instance 2: [name1-bis: 2, name2-bis: 3, name3-bis: 4]

If we base the deep copy logic on the previous schema, this should be the result: Deep copy

As you may notice, the result of the execution of the main method is different from the logic of the schema above.

Any explanation will be welcome.

Mysia answered 28/7, 2016 at 16:4 Comment(5)
Wow, you really spent a lot of time here. Lets hope that you didn't actually provide too much information (me for example, I am much more tempted to hop to the next question, just because it will take me probably 10 minutes to digest your input). But just for the sake of it: tried to use clone methods instead of copyOf() (which me thinks doesn't waste time cloning stuff, but just does a shallow copy)Corn
Arrays.copyOf does a shallow copy.Nuncle
You can only see if you have a deep or shallow copy by comparing the two instances. Basically they should have the "same content" but not reference to the same Object if it's a deep copy. You should know some things about references and stuff if you are developing with Java (or other languages and how they're handling it).Anthropomorphosis
Potential duplicate of Does Arrays.copyOf produce a shallow or a deep copy?Iy
@Anthropomorphosis I was misunderstanding things because I based my logic on the fact that Arrays.copyOf performs a deep copy. Now that many of you confirms that it produces shallow copy, things are more clearMysia
A
4

I am trying to understand the concept of shallow vs deep copy in Java.

In Java you pass around and store references to objects not the objects themselves.
So when you have an NameValue[] array the array does not contain the objects NameValue but references to the objects.
So when you do a shallow copy to NameValue[] array2 it means you are just copying the references from one array to the other. Which effectively means that now both array and array2 refer to exactly the same objects and any change you do from array[2] will be visible from array2[2] (same object).

When you deep copies you copy each object completely to another memory area and you keep a reference to that new object in your new array.
This way the 2 arrays now refer to different objects and any change to array[2] are not visible from array2[2]

Update:
This does not apply to primitives that do store the actual value and not a reference.
So an int[] a when you copy you get a copy of the values (i.e. deep copy in a sense) because a[2] contains the value itself and not the reference to the value.

Argillaceous answered 28/7, 2016 at 16:36 Comment(3)
Good explanation. Some one should mention though that every copy method found in the JDK does actually copy it's contents. But the difference between shallow and deep copy becomes apparent once you copy an array of objects instead of primitives.Zany
It's the explanation I was looking for, especially the way Java treats the copying of arrays with primitives Vs The copying of arrays with references...Along with @Iy answer's, I assume that on one hand the methods that I referenced in my question produce shallow copy when working with arrays with references, on the other hand, they produce 'a more like deep copy' when working with primitivesMysia
@AnassBouchkioui:A primitive variable is a place in memory that contains the value you assign. An object variable is a place in memory that contains a reference to the actual object's location. So when you assign a variable you move the contents of one place to another. In the case of primitive the content is the value itself. In case of references the content is the object's reference.Argillaceous
I
5

I don't know where you read that copyOf() performs a deep copy, because that is just plain wrong.

Quoting javadoc of Arrays.copyOf(T[] original, int newLength):

For all indices that are valid in both the original array and the copy, the two arrays will contain identical values.

That means it's a shallow copy. To be a deep copy, the values would have to point to different objects, since the referenced object would have to be a copy too.

To perform a deep copy, you have to iterate the array and copy the values. Java can't do that for you, because it doesn't know how to copy the object.

E.g. how would Java know how to copy a NameValue object? clone()? Copy constructor? Serialize+Deserialize? Factory method? Other means?

Iy answered 28/7, 2016 at 16:13 Comment(10)
Adding to your comment, if you search the source code of the copyOf() method, you'll end up finding this: System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); - In the end, both are the same :PBega
Yup. The copyOf() methods are just convenience methods for System.arraycopy().Iy
Java is unfortunately absolutely AWFUL with deep-copying stuff. As Andreas said, you have to iterate and copy everything yourself. You may try the Cloneable thing but it just doesn't really work.Bega
And what if the values aren't references? That quote specifically says the two arrays will contain identical values, not identical references. 3 and 3 are the same value, that doesn't mean they're the same reference.Photoengraving
@Iy @g0dkar I had this information from the book "Oracle Certified Associate, Java SE 7 Programmer Study Guide" of the author Richard M. Reese. In the summary of the section Copying Arrays of Chapter 4, it is commented that Using the Arrays.copyOf method Performs a deep copy of the entire array...Basing my logic on this information I didn't go far in search of the implementation of the Arrays.copyOf methodMysia
Well, Mr. Reese got it wrong. Did you check to see if there's an errata for it?Iy
I didn't check for any newer version of the book, I will see if there is an errata of this partMysia
@Photoengraving The "literal" values are copied. 3 is copied into another 3. References are, under the hood, just numbers that points to objects. You may copy the number itself but what the number points to won't be copied :) - Imagine a reference as 0x1A2B3C, it'll be copied just like that and will point to the object number 0x1A2B3C which is the same object pointed to by the previous array.Bega
@g0dkar was more of a rhetorical question :P, but while we're discussing it, could you say it's ALWAYS a shallow copy then? Because in the purpose of primitive types, it is a deep copy. It specifically says the value, and I don't think you can say the value of an object is its reference in any context, could you? It containing identical "values" more sounds like a deep copy than a shallow, regardless if an array is just a container of the references to objects, not a container of the objects itselfPhotoengraving
@Photoengraving Primitives are plain values, and shallow vs deep doesn't apply. The value is simply copied. The distinction applies to object references, differentiating between copying the reference (shallow) vs copying the object (deep).Iy
A
4

I am trying to understand the concept of shallow vs deep copy in Java.

In Java you pass around and store references to objects not the objects themselves.
So when you have an NameValue[] array the array does not contain the objects NameValue but references to the objects.
So when you do a shallow copy to NameValue[] array2 it means you are just copying the references from one array to the other. Which effectively means that now both array and array2 refer to exactly the same objects and any change you do from array[2] will be visible from array2[2] (same object).

When you deep copies you copy each object completely to another memory area and you keep a reference to that new object in your new array.
This way the 2 arrays now refer to different objects and any change to array[2] are not visible from array2[2]

Update:
This does not apply to primitives that do store the actual value and not a reference.
So an int[] a when you copy you get a copy of the values (i.e. deep copy in a sense) because a[2] contains the value itself and not the reference to the value.

Argillaceous answered 28/7, 2016 at 16:36 Comment(3)
Good explanation. Some one should mention though that every copy method found in the JDK does actually copy it's contents. But the difference between shallow and deep copy becomes apparent once you copy an array of objects instead of primitives.Zany
It's the explanation I was looking for, especially the way Java treats the copying of arrays with primitives Vs The copying of arrays with references...Along with @Iy answer's, I assume that on one hand the methods that I referenced in my question produce shallow copy when working with arrays with references, on the other hand, they produce 'a more like deep copy' when working with primitivesMysia
@AnassBouchkioui:A primitive variable is a place in memory that contains the value you assign. An object variable is a place in memory that contains a reference to the actual object's location. So when you assign a variable you move the contents of one place to another. In the case of primitive the content is the value itself. In case of references the content is the object's reference.Argillaceous
P
1

I think there is a little missunderstanding that Arrays.copyOf() produces a deep copy.

Arrays.copyOf() makes a new array that contains old refrences to objects that are not being copied and as the link that I've added explains in the case of nested arrays they will not get copied and hence it can't be considered a deep copy but a shallow copy.

See this for more information.

Past answered 28/7, 2016 at 16:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.