Search code examples
javahashmapclone

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


Java doc says HashMap.clone() returns a shallow copy.

So I expect that if I change the value of some key in original HashMap, the cloned one will also see the change. Thus, I have this:

public class ShallowCopy {
    public static void main(String[] args) {
        HashMap<Integer, String> map = new HashMap<Integer, String>();
        map.put(2,"microsoft");
        map.put(3,"yahoo");
        map.put(1,"amazon");

        // Type safety: Unchecked cast from Object to
        // HashMap<Integer, String> Java(16777761)
        Map<Integer, String> mClone = 
            (HashMap<Integer, String>)map.clone();
        String previous = map.replace(3, "google");
        System.out.println(previous); // yahoo
        System.out.println(map.get(3)); // google
        System.out.println(mClone.get(3)); // yahoo, but why?
    }
}

In my code, I called HashMap.replace() and I see the value in map is changed from "yahoo" to "google". Strangely, the last line prints the previous value when looking for it in mClone.

But the fact is it prints "yahoo" not "google" as I expected. Where does it get wrong, please kindly fix my understandings?

Plus: I also got a compiler warning as I commented in my code(Java(16777761)), how to fix it?


Solution

  • 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.