How to Copy Hashmap (Not Shallow Copy) in Java

How to copy HashMap (not shallow copy) in Java

You're right that a shallow copy won't meet your requirements. It will have copies of the Lists from your original map, but those Lists will refer to the same List objects, so that a modification to a List from one HashMap will appear in the corresponding List from the other HashMap.

There is no deep copying supplied for a HashMap in Java, so you will still have to loop through all of the entries and put them in the new HashMap. But you should also make a copy of the List each time also. Something like this:

public static HashMap<Integer, List<MySpecialClass>> copy(
HashMap<Integer, List<MySpecialClass>> original)
{
HashMap<Integer, List<MySpecialClass>> copy = new HashMap<Integer, List<MySpecialClass>>();
for (Map.Entry<Integer, List<MySpecialClass>> entry : original.entrySet())
{
copy.put(entry.getKey(),
// Or whatever List implementation you'd like here.
new ArrayList<MySpecialClass>(entry.getValue()));
}
return copy;
}

If you want to modify your individual MySpecialClass objects, and have the changes not be reflected in the Lists of your copied HashMap, then you will need to make new copies of them too.

Deep Copy of HashMap in Java

Both ways you showed doesn't copy the int[], i.e. not copying as deep as you want.

The first way actually returns a Map<Character, List<int[]>> rather than the HashMap<Character, ArrayList<int[]>> you want. I would recommend that you program to interfaces and change your method to return a Map<Character, List<int[]>> instead, then you can use streams like this:

public Map<Character, List<int[]>> copy (HashMap<Character, ArrayList<int[]>> original) {
Map<Character, List<int[]>> copy = original.entrySet().stream()
.collect(Collectors.toMap(
Entry::getKey,
e -> e.getValue().stream().map(x -> Arrays.copyOf(x, x.length)).collect(Collectors.toList())
));
return copy;
}

If you really don't want to change the return type, you'd have to pass more arguments to specify the concrete types you want:

public HashMap<Character, ArrayList<int[]>> copy (HashMap<Character, ArrayList<int[]>> original) {
HashMap<Character, ArrayList<int[]>> copy = original.entrySet().stream()
.collect(Collectors.toMap(
Entry::getKey,
e -> e.getValue().stream().map(x -> Arrays.copyOf(x, x.length)).collect(Collectors.toCollection(ArrayList::new)),
(x, y) -> x,
HashMap::new
));
return copy;
}

Otherwise, you can use the traditional for loops:

public HashMap<Character, ArrayList<int[]>> copy (HashMap<Character, ArrayList<int[]>> original) {
HashMap<Character, ArrayList<int[]>> copy = new HashMap<>();
for (Map.Entry<Character, ArrayList<int[]>> entry : original.entrySet()) {
ArrayList<int[]> listCopy = new ArrayList<>();
for (int[] array: entry.getValue()) {
listCopy.add(Arrays.copyOf(array, array.length));
}
copy.put(entry.getKey(), listCopy);
}
return copy;
}

Notice that in all three code snippets, I have used Arrays.copyOf to copy the int[].

why shallow copy on Hashmap is not reflecting

Shallow copying means that it is not applied to copying objects inside the original objects. That is, if you have an object containing a collection and you clone this object, the collection itself is not copied and may become shared between multiple instances:

public class Test implements Cloneable {
private List<String> innerList = new ArrayList<>();

public void addBaz(String baz) {
innerList.add(baz);
}

public List<String> getInnerList() {
return innerList;
}

public void setInnerList(List<String> innerList) {
this.innerList = innerList;
}

public static void main(String[] args) throws CloneNotSupportedException {
Test bar1 = new Test();
bar1.addBaz("baz1");
bar1.addBaz("baz2");
bar1.addBaz("baz3");

System.out.println("bar1 before cloning: " + bar1.getInnerList());

Test cloneBar = (Test) bar1.clone();

System.out.println("clone: " + cloneBar.getInnerList());

bar1.addBaz("baz4");
System.out.println("clone after adding to original: " + cloneBar.getInnerList());

cloneBar.addBaz("cloneBaz1");
System.out.println("bar1 after adding to clone: " + bar1.getInnerList());
}
}

The output is:

bar1 before cloning: [baz1, baz2, baz3]
clone: [baz1, baz2, baz3]
clone after adding to original: [baz1, baz2, baz3, baz4]
bar1 after adding to clone: [baz1, baz2, baz3, baz4, cloneBaz1]

Also, if you had added to the HashMap other object than String, you could see, that shallow copy works there too - the original and cloned map contain shared objects and therefore contents of both maps may change:

public class Test implements Cloneable {
private String foo;

// getter/setter/toString ...

public static void main(String[] args) {
HashMap<String, Test> map = new HashMap<>();
map.put("a1", new Test("a1"));
map.put("a2", new Test("a2"));
map.put("a3", new Test("a3"));

System.out.println("map before cloning: " + map);

HashMap<String, Test> cmap = (HashMap<String, Test>) map.clone();

System.out.println("clone: " + cmap);

map.put("b1", new Test("b1"));
System.out.println("clone after adding to original: " + cmap);

cmap.put("c1", new Test("c1"));
System.out.println("original after adding to clone: " + map);

map.get("a1").setFoo("new a1");
System.out.println("clone after modifying object inside original: " + cmap);

cmap.get("a2").setFoo("cloned a2");
System.out.println("original after modifying the clone: " + map);
}
}

The output is:

map before cloning: {a1=->a1, a2=->a2, a3=->a3}
clone: {a1=->a1, a2=->a2, a3=->a3}
clone after adding to original: {a1=->a1, a2=->a2, a3=->a3}
original after adding to clone: {a1=->a1, a2=->a2, a3=->a3, b1=->b1}
clone after modifying object inside original: {a1=->new a1, a2=->a2, a3=->a3, c1=->c1}
original after modifying the clone: {a1=->new a1, a2=->cloned a2, a3=->a3, b1=->b1}

Does shallow copy work as deep copy in case of immutable objects in java?

There is no need for a deep copy because the Point objects are immutable and cannot change. This is asserted in:

Since the contents of the Map are immutable

If the Point objects were mutable you would in deed have to make a deep copy.

The difference between the two implementations if getLocations refers to visibility to changes in the "structure" of the map. The first implementation returns an unmodifiable view of a copy of the map. If locations are added or removed after the method returns, it will not be visible in the map returned by this method. The second returns an unmodifiable view of the map itself. Any changes made after the method returns will be visible to the caller.

HashMap.clone() returns shallow copy, but doesn't reflect the Value Change

TL;DR

After the cloning operation the values are simply references to the same object. So if you were to modify one reference in one map, the other would also be modified. But you didn't modify the object you replaced it. At that point it became distinct from a reference perspective.

Example

The clone operation is working just as you presumed. But you are interpreting the results incorrectly. Consider the following class.

class FooClass {
int a;
public FooClass(int a) {
this.a = a;
}
public void setA(int a) {
this.a = a;
}
@Override
public String toString() {
return a + "";
}
}

And now create a map and its clone.

HashMap<Integer, FooClass> map = new HashMap<>();
map.put(10, new FooClass(25));
HashMap<Integer,FooClass> mClone = (HashMap<Integer,FooClass>)map.clone();

The values of each key are the same object reference. As shown by the following:

System.out.println(System.identityHashCode(map.get(10)));
System.out.println(System.identityHashCode(mClone.get(10)));

prints

1523554304
1523554304

So if I modify one, it will modify the other.

The same was true for the String values of your maps. But when you replaced "yahoo" with "google" you didn't modify the String you replaced it with a different Object.

If I were to do the same for FooClass, here is the result.

System.out.println("Modifying same object");
mClone.get(10).setA(99);
System.out.println(map.get(10));
System.out.println(mClone.get(10));

prints

Modifying same object
99
99

But if I were to replace the object with a new one.

System.out.println("Replacing the instance");
FooClass previous = mClone.replace(10, new FooClass(1000));
System.out.println("Previous = " + previous);
System.out.println("map: " + map.get(10));
System.out.println("mClone: " + mClone.get(10));

prints

Replacing the instance
Previous = 99
map: 99
mClone: 1000

And this latter operation is what you did.

Deep copying a Java Map with values that are Sets

There was some discussion on this here for deep cloning:

Java HashMap - deep copy

It's hard in this situation though as the new map and set need to rebuild themselves. Do you also need to clone the contents of the Set? If so then you could just serialize then de-serialize the whole collection.

Iterating yourself will almost certainly be faster and will let you control how deep the cloning goes. Serializing will be slower but so long as everything in the object tree is serializable it will copy everything in the object tree and maintain things like circular references etc. (For example if you had 2 keys in the map pointing to the same Set object then iterating will split that into 2 sets, serializing will maintain the link).

Java HashMap - deep copy

Take a look at Deep Cloning, on Google Code you can find a library. You can read it on https://github.com/kostaskougios/cloning.

How it works is easy. This can clone any object, and the object doesnt have to implement any interfaces, like serializable.

Cloner cloner = new Cloner();
MyClass clone = cloner.deepClone(o);
// clone is a deep-clone of o

Be aware though: this may clone thousands of objects (if the cloned object has that many references). Also, copying Files or Streams may crash the JVM.

You can, however, ignore certain instances of classes, like streams et cetera. It's worth checking this library and its source out.



Related Topics



Leave a reply



Submit