javainterfacehashmapequalslinkedhashmap

Using equals to compare map values returns false even when values and insertion order are the same


I have two Map objects: one HashMap and one LinkedHashMap. Both contain the same values (e.g., [1, 2]), and for the LinkedHashMap, the insertion order is preserved. However, when I compare the collections returned by the values() function of these maps using the equals() method, the comparison returns false.

I initially assumed this might be due to HashMap not preserving insertion order. But when I perform the same comparison with two LinkedHashMap objects, where the insertion order is indeed preserved, the equals() method still returns false.

Can someone explain why the equals() method returns false in both cases, even though:

import java.util.LinkedHashMap;
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {

        // HashMap example

        Map<String, Integer> map1 = new HashMap<>();
        map1.put("a", 1);
        map1.put("b", 2);

        Map<String, Integer> map2 = new HashMap<>();
        map2.put("x", 1);
        map2.put("y", 2);

        System.out.println("map1 values: " + map1.values());  // prints [1, 2]
        System.out.println("map2 values: " + map2.values());  // prints [1, 2]

        System.out.println("map1.values().equals(map2.values()): " + map1.values().equals(map2.values())); // returns false


        // LinkedHashMap example

        Map<String, Integer> map1l = new LinkedHashMap<>();
        map1l.put("a", 1);
        map1l.put("b", 2);

        Map<String, Integer> map2l = new LinkedHashMap<>();
        map2l.put("x", 1);
        map2l.put("y", 2);

        System.out.println("map1l values: " + map1l.values()); // prints [1, 2]
        System.out.println("map2l values: " + map2l.values()); // prints [1, 2]

        System.out.println("map1l.values().equals(map2l.values()): " + map1l.values().equals(map2l.values())); // still returns false
    }
}

Solution

  • The reason why the collections returned by the two instances of HashMap are considered not equal, even though they contain the same elements in the same order, is because the actual implementation of Collection returned by HashMap.values() is an inner class of HashMap called Values, which doesn't provide a proper definition of the equals() method.

    HashMap$Values inherits the definition of its equals() method from the Object class, which only compares the memory address of the current object with the given argument. Hence, since the method compares two different objects residing in two different memory addresses, the method returns false.

    The same thing also happens when you're comparing the collections returned by your LinkedHashMap instances. In this case though, you're comparing two instances of LinkedHashMap$LinkedValues that don't provide a proper definition of equals() as well, inheriting its definition from Object.

    If you need to compare the values returned by a Map implementation, you could pass the collection returned by values() to the conversion constructor of:

    Also, make sure that the values' class provides a proper definition of equals() and hasCode() according to the general contract of hashcode, since the definitions of equals() offered by ArrayList and HashSet are based on the equals() of their elements.

    //Comparing the LinkedHashMap values via ArrayList
    List<Integer> list1 = new ArrayList<>(map1l.values());
    List<Integer> list2 = new ArrayList<>(map2l.values());
    System.out.println(list1.equals(list2));   //true
    
    //Comparing the LinkedHashMap values via HashSet
    Set<Integer> set1 = new HashSet<>(map1l.values());
    Set<Integer> set2 = new HashSet<>(map2l.values());
    System.out.println(set1.equals(set2));   //true