Search code examples
javaequalscomparatorhashcodetreemap

Java TreeMap: Different object after "put"?


I use a class derived from TreeMap with my own comparator as keys in a LinkedHashMap. Working with this construct I found some weird behaviour I could not explain myself. Maybe one of you can help. I tried to reproduce my issue with primitives. When I create a TreeMap of primitive types, the natural sort ordering should suffice and do not need a comparator in the constructor of the TreeMap, right?!

Here is the MWE:

package treemapputtest;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;

public class TreeMapPutTest {

    public static void main(String[] args) {

        System.out.println("simple:");
        simpleTest();

        System.out.println("\n\ncomplex:");
        complexTest();
    }

    private static void simpleTest(){

        TreeMap<Integer,String> map = new TreeMap<>();

        System.out.println("map: " + map.hashCode() + " | " + Integer.toHexString(map.hashCode()));

        map.put(1, "a");

        System.out.println("map: " + map.hashCode() + " | " + Integer.toHexString(map.hashCode()));

        map.put(2, "b");

        System.out.println("map: " + map.hashCode() + " | " + Integer.toHexString(map.hashCode()));

    }

    private static void complexTest(){

        TreeMap<Integer,String> internalMap = new TreeMap<>();
        internalMap.put(1, "a");
        internalMap.put(2, "b");

        System.out.println("prior: " + internalMap.hashCode() + " | " + Integer.toHexString(internalMap.hashCode()));

        LinkedHashMap<TreeMap<Integer,String>,Double> myMap = new LinkedHashMap<>();
        myMap.put(internalMap, 1.0);

        doSomethingWithMyInternalMap(myMap.keySet().iterator().next());

        System.out.println("after:");
        for (Map.Entry<TreeMap<Integer,String>,Double> entry : myMap.entrySet()){
            System.out.println("  " + Integer.toHexString(entry.getKey().hashCode()));
        }

    }

    private static void doSomethingWithMyInternalMap(TreeMap<Integer,String> intern){
        intern.put(3, "c");
    }
}

The output is:

simple:
map: 0 | 0
map: 96 | 60
map: 192 | c0

complex:
prior: 192 | c0
after:
  120

So my question is: Why does the result of hashCode() change when I add stuff to the TreeMap? For the TreeMap alone this is not a big deal, but as this creates a "new object"/the reference to the old object is changed, I get errors after updating the TreeMap in the LinkedHashMap.

The Object API says for hashCode():

The general contract of hashCode is: Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified.

Does putting additional stuff in the TreeMap change something in the equals() method of TreeMap? Do I have to somehow override equals() and hashCode()?


Solution

  • I think you have misunderstanding about hashCode here. Let's emphasize the point in the text you quoted here:

    The general contract of hashCode is: Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified.

    Whenever you add (or remove) data in a map, you're changing the information used in its equals method - an empty map isn't equal to a map with [1->a] in it, and a map with [1->a] isn't equal to a map with [1->a; 2->b].

    This has nothing to do with creating a new object, and the reference to the old map does not change. If you call System.identityHashCode(map) instead of map.hashCode() you'll see the object reference does not change no matter how many times you call put on it.