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 List
s from your original map, but those List
s 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 List
s 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
What's the Purpose of Meta-Inf
Download Attachments Using Java Mail
Eclipse Error: Could Not Find or Load Main Class
Why Is My Jlabel Not Showing Up
How to Deal with the Urisyntaxexception
Differencebetween a Hashmap and a Treemap
How to Convert Utf-8 to Us-Ascii in Java
Things Possible in Intellij That Aren't Possible in Eclipse
Java Creating a New Objectinputstream Blocks
How to Set Java Max Heap Size for Running from a Jar File
Differencebetween Canonical Name, Simple Name and Class Name in Java Class
Installing Java on Os X 10.9 (Mavericks)
Why Are Interface Method Invocations Slower Than Concrete Invocations
How to Remove Element from Arraylist by Checking Its Value