In this Q&A, we'll go over what's shallow copy and deep copy of collections and how to do it.
To understand shallow copy of collections let's first review the code below:
ArrayList<Instant> al = new ArrayList<>();
for(int i=0; i<10; ++i)
al.add(Instant.now());
ArrayList<Instant> al2 = new ArrayList<>(al);
assertEquals("ArrayList reference don't match", false, al == al2));
assertEquals("Element equals match", true, al2.get(0).equals(al.get(0)));
assertEquals("Element reference match", true, al2.get(0) == al.get(0));
ArrayList al2 is a copy of al. Reference equality of the collection object won't match. But for individual elements within the List both equals and reference equality matches.
Individual elements are reference copied. New objects are not created for the List elements. This is shallow copy. Shallow copy is the default behavior
Deep copy involves creating new objects for List elements. To deep copy there are a few options:
Option I - Use Apache Commons SerializationUtils
If the List element is Serializable, then SerializationUtils will clone the object by serializing and de-serializing the element. Clone will fail if any sub objects are not serializable. See demo code below for usage.
Option II - Serialize using JSON libraries
This is a variant of Option I. Difference here is we are serializing the object to JSON and back using JSON libraries like GSON or Jackson
Option III - Clone the individual elements
Option I and II will deep copy all sub objects including immutable objects. Deep copy of just the mutable objects is desirable. See demo code below on how to clone just mutable objects.
package com.javahowdoi.string;
import org.apache.commons.lang3.SerializationUtils;
import java.time.Instant;
import java.util.ArrayList;
import static junit.framework.TestCase.assertEquals;
public class CloneDemo {
private static class Inner1 {
public int i;
public int j;
Inner1(int i, int j) {
this.i = i;
this.j = j;
}
@Override
public boolean equals(Object o) {
if(! (o instanceof Inner1 ))
return false;
Inner1 i1 = (Inner1) o;
return (i1.i == this.i && i1.j == this.j );
}
}
private static class Inner2 implements Cloneable{
public String s = "";
public Inner1 i1 = new Inner1(1,2);
Inner2(String s ) {
this.s = s;
}
@Override
public Inner2 clone() {
// create new Inner1 object
// do not clone immutable string object
return( new Inner2(this.s) );
}
@Override
public boolean equals(Object o) {
if(! (o instanceof Inner2 ))
return false;
Inner2 i2 = (Inner2) o;
return ( i2.s.equals(this.s) && i2.i1.equals(this.i1));
}
}
public static void main(String[] args) {
ArrayList<Instant> al = new ArrayList<>();
for(int i=0; i<10; ++i)
al.add(Instant.now());
// deep copy for immutable and mutable objects
ArrayList<Instant> al2 = SerializationUtils.clone(al);
//shallow copy
ArrayList<Instant> al3 = new ArrayList<>(al);
for(int i=0; i<10; ++i) {
assertEquals("Equals match", true, al2.get(i).equals(al.get(i)));
assertEquals("Equals match", true, al3.get(i).equals(al.get(i)));
assertEquals("Reference don't match", false, al2.get(i) == al.get(i));
assertEquals("Equals match", true, al3.get(i) == al.get(i));
}
ArrayList<Inner2> ai = new ArrayList<>();
for(int i=0; i<10; ++i)
ai.add( new Inner2( new String("abc")));
ArrayList<Inner2> ai2 = new ArrayList<>();
// shallow copy for immutable objects
// deep copy for mutable objects
for(int i=0; i<10; ++i)
ai2.add( ai.get(i).clone());
for(int i=0; i<10; ++i) {
assertEquals("Equals match", true, ai.get(i).equals(ai2.get(i)));
assertEquals("Reference don't match for Inner1", false, ai.get(i).i1 == ai2.get(i).i1);
assertEquals("Object reference matches for String", true, ai.get(i).s == ai2.get(i).s);
}
}
}
Comments
Post a Comment