Java’s HashMap (AbstractMap really) hashCode() implementation sums the hash codes of its entries rather than multiplies them by a prime number like String.hashCode() does. This can cause weird collisions in very similar maps.
Example:
Map<String, String> mapOne = new HashMap<>();
mapOne.put("first", "valueB");
mapOne.put("second", "valueB");
Map<String, String> mapTwo = new HashMap<>();
mapTwo.put("first", "valueA");
mapTwo.put("second", "valueC");
These two maps are different, but their hash codes are the same due to the fact that HashMap sums children hash codes rather than multiplies them and the entries values in this case happen to be perfectly collide with one another.
Just for clarity, HashMap's Map.Entry implementation is HashMap.Node, which creates hash code as:
key.hashCode() ^ value.hashCode()
Here's some code exemplifying the problem:
Map<String, String> mapOne = new HashMap<>(); mapOne.put("first", "valueB"); mapOne.put("second", "valueB"); Map<String, String> mapTwo = new HashMap<>(); mapTwo.put("first", "valueA"); mapTwo.put("second", "valueC"); System.out.println("\"first\".hashCode(): " + "first".hashCode()); System.out.println("\"second\".hashCode(): " + "second".hashCode()); System.out.println("\"valueA\".hashCode(): " + "valueA".hashCode()); System.out.println("\"valueB\".hashCode(): " + "valueB".hashCode()); System.out.println("\"valueC\".hashCode(): " + "valueC".hashCode()); System.out.println("Simulated mapOne hashcode math:"); int firstValueBHashCode = ("first".hashCode() ^ "valueB".hashCode()); System.out.println("Map one's first ^ valueB hashcode: " + firstValueBHashCode); int secondValueBHashCode = ("second".hashCode() ^ "valueB".hashCode()); System.out.println("Map one's second ^ valueB hashcode: " + secondValueBHashCode); int mapOneHashCode = firstValueBHashCode + secondValueBHashCode; System.out.println("Map one's hashcode, (first ^ valueB) + (second ^ valueB): " + mapOneHashCode); System.out.println("Simulated mapTwo hashcode math:"); int firstValueAHashCode = ("first".hashCode() ^ "valueA".hashCode()); System.out.println("Map two's first ^ valueA hashcode: " + firstValueAHashCode); int secondValueCHashCode = ("second".hashCode() ^ "valueC".hashCode()); System.out.println("Map two's second ^ valueC hashcode: " + secondValueCHashCode); int mapTwoHashCode = firstValueAHashCode + secondValueCHashCode; System.out.println("Map two's hashcode, (first ^ valueA) + (second ^ valueB): " + mapTwoHashCode); System.out.println("Final result:"); System.out.println("Map one's hashcode, (first ^ valueB) + (second ^ valueB): " + mapOneHashCode); System.out.println("Map two's hashcode, (first ^ valueA) + (second ^ valueB): " + mapTwoHashCode); System.out.println("Map hashcodes match: " + (mapOneHashCode == mapTwoHashCode));
Output from above looks like this:
"first".hashCode(): 97440432 "second".hashCode(): -906279820 "valueA".hashCode(): -823812880 "valueB".hashCode(): -823812879 "valueC".hashCode(): -823812878 Simulated mapOne hash code math: Map one's first ^ valueB hash code: -886354367 Map one's second ^ valueB hash code: 119462021 Map one's hash code, (first ^ valueB) + (second ^ valueB): -766892346 Simulated mapTwo hash code math: Map two's first ^ valueA hash code: -886354368 Map two's second ^ valueC hash code: 119462022 Map two's hash code, (first ^ valueA) + (second ^ valueB): -766892346 Final result: Map one's hash code, (first ^ valueB) + (second ^ valueB): -766892346 Map two's hash code, (first ^ valueA) + (second ^ valueB): -766892346 Map hash codes match: true
To solve this problem, write your own utility function to compute the hash code with a prime number multiplier as generally recommended, like so:
public static <T extends Object, U extends Object> int createMapHashCode(Map<T,U> map) { int hashCode = 0; for (Map.Entry<T, U> entry : map.entrySet()) { if (entry != null) { hashCode = (hashCode * 31) + entry.hashCode(); } } return hashCode; }