java – Comparing two maps

java – Comparing two maps

Quick Answer

You should use the equals method since this is implemented to perform the comparison you want. toString() itself uses an iterator just like equals but it is a more inefficient approach. Additionally, as @Teepeemm pointed out, toString is affected by order of elements (basically iterator return order) hence is not guaranteed to provide the same output for 2 different maps (especially if we compare two different maps).

Note/Warning: Your question and my answer assume that classes implementing the map interface respect expected toString and equals behavior. The default java classes do so, but a custom map class needs to be examined to verify expected behavior.

See: http://docs.oracle.com/javase/7/docs/api/java/util/Map.html

boolean equals(Object o)

Compares the specified object with this map for equality. Returns true
if the given object is also a map and the two maps represent the same
mappings
. More formally, two maps m1 and m2 represent the same
mappings if m1.entrySet().equals(m2.entrySet())
. This ensures that the
equals method works properly across different implementations of the
Map interface.

Implementation in Java Source (java.util.AbstractMap)

Additionally, java itself takes care of iterating through all elements and making the comparison so you dont have to. Have a look at the implementation of AbstractMap which is used by classes such as HashMap:

 // Comparison and hashing

    /**
     * Compares the specified object with this map for equality.  Returns
     * <tt>true</tt> if the given object is also a map and the two maps
     * represent the same mappings.  More formally, two maps <tt>m1</tt> and
     * <tt>m2</tt> represent the same mappings if
     * <tt>m1.entrySet().equals(m2.entrySet())</tt>.  This ensures that the
     * <tt>equals</tt> method works properly across different implementations
     * of the <tt>Map</tt> interface.
     *
     * <p>This implementation first checks if the specified object is this map;
     * if so it returns <tt>true</tt>.  Then, it checks if the specified
     * object is a map whose size is identical to the size of this map; if
     * not, it returns <tt>false</tt>.  If so, it iterates over this maps
     * <tt>entrySet</tt> collection, and checks that the specified map
     * contains each mapping that this map contains.  If the specified map
     * fails to contain such a mapping, <tt>false</tt> is returned.  If the
     * iteration completes, <tt>true</tt> is returned.
     *
     * @param o object to be compared for equality with this map
     * @return <tt>true</tt> if the specified object is equal to this map
     */
    public boolean equals(Object o) {
        if (o == this)
            return true;

        if (!(o instanceof Map))
            return false;
        Map<K,V> m = (Map<K,V>) o;
        if (m.size() != size())
            return false;

        try {
            Iterator<Entry<K,V>> i = entrySet().iterator();
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                K key = e.getKey();
                V value = e.getValue();
                if (value == null) {
                    if (!(m.get(key)==null && m.containsKey(key)))
                        return false;
                } else {
                    if (!value.equals(m.get(key)))
                        return false;
                }
            }
        } catch (ClassCastException unused) {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }

        return true;
    }

Comparing two different types of Maps

toString fails miserably when comparing a TreeMap and HashMap though equals does compare contents correctly.

Code:

public static void main(String args[]) {
HashMap<String, Object> map = new HashMap<String, Object>();
map.put(2, whatever2);
map.put(1, whatever1);
TreeMap<String, Object> map2 = new TreeMap<String, Object>();
map2.put(2, whatever2);
map2.put(1, whatever1);

System.out.println(Are maps equal (using equals): + map.equals(map2));
System.out.println(Are maps equal (using toString().equals()):
        + map.toString().equals(map2.toString()));

System.out.println(Map1:+map.toString());
System.out.println(Map2:+map2.toString());
}

Output:

Are maps equal (using equals):true
Are maps equal (using toString().equals()):false
Map1:{2=whatever2, 1=whatever1}
Map2:{1=whatever1, 2=whatever2}

As long as you override equals() on each key and value contained in the map, then m1.equals(m2) should be reliable to check for maps equality.

The same result can be obtained also by comparing toString() of each map as you suggested, but using equals() is a more intuitive approach.

May not be your specific situation, but if you store arrays in the map, may be a little tricky, because they must be compared value by value, or using Arrays.equals(). More details about this see here.

java – Comparing two maps

Comparing two Maps:

declare:

enum Activity{
    ADDED,
    REMOVED,
    MODIFIED
}

@Data
@NoArgsConstructor
@AllArgsConstructor
static class FileStateRow {
    String key;
    String value;
    Activity activity;
}

BiFunction<Map<String, Object>, Map<String, Object>, Map<String, FileStateRow>> 
mapCompare = (newMap, oldMap) -> {
    Map<String, FileStateRow> resMap = new HashMap<>();
    newMap.forEach((k, v) -> {
        if (!oldMap.containsKey(k)) {
            System.out.println(newMap key: + k +  is missing in oldMap - ADDED);
            resMap.put(k, new FileStateRow(k, (String) v, Activity.ADDED));
        } else {
            if (oldMap.get(k) != null && !oldMap.get(k).equals(v)) {
                System.out.println(newMap value change for key: + k + , old: + oldMap.get(k) + , new  + v);
                resMap.put(k, new FileStateRow(k, (String) v, Activity.MODIFIED));
            }
        }
    }); 

    oldMap.forEach((k, v) -> {
        if (!newMap.containsKey(k)) {
            System.out.println(newMap key: + k +  is missing in oldMap);
            resMap.put(k, new FileStateRow(k, (String) v, Activity.REMOVED));
        }
    }); 
    return resMap;
};

use:

Map<String, Object> map1 = .. // initiate and put values in..
Map<String, Object> map2 = .. // initiate and put values in..

// compare...
Map<String, FileStateRow> res = mapCompare.apply(map1, map2);

// print results
res.forEach((k, v) -> {
     System.out.println(key: + k + ,  value  + v);
});

by yl.

Leave a Reply

Your email address will not be published.