20

I have two maps:

Map<String, Object> map1;
Map<String, Object> map2;

I need to receive difference between these maps. Does exist may be apache utils how to receive this difference? For now seems need take entry set of each map and found diff1 = set1 - set2 and diff2 = set2- set1. After create summary map =diff1 + diff2 It looks very awkwardly. Does exist another way? Thanks.

user710818
  • 23,228
  • 58
  • 149
  • 207

7 Answers7

45

How about google guava?:

Maps.difference(map1,map2)
Mirza
  • 53
  • 7
Koerr
  • 15,215
  • 28
  • 78
  • 108
  • 2
    Thanks. I thought about guava, but for this need introduce new library in project, bette don't do this. – user710818 Oct 04 '12 at 06:54
  • 6
    @user710818 You wouldn't regret it - it is a great library – vitaly Oct 04 '12 at 06:55
  • 3
    @user710818 You should use it in your project – Koerr Oct 04 '12 at 06:56
  • 2
    It can be achieved in java if simple math is used. There is no need to introduce other library for that. – Amit Deshpande Oct 04 '12 at 07:24
  • If the two maps have entries that differ in values but having same keys, then this library is quite handy `code` `Map> entriesDiffering()` Returns an unmodifiable map describing keys that appear in both maps, but with different values. – Narasimha Oct 16 '18 at 20:32
  • https://stackoverflow.com/review/suggested-edits/21930992 this is a good java tool but keep in mind that it does not do a deep comparison only on first level. – Evandro Coan Jan 15 '19 at 14:08
23

Here is a simple snippet you can use instead of massive Guava library:

public static <K, V> Map<K, V> mapDifference(Map<? extends K, ? extends V> left, Map<? extends K, ? extends V> right) {
    Map<K, V> difference = new HashMap<>();
    difference.putAll(left);
    difference.putAll(right);
    difference.entrySet().removeAll(right.entrySet());
    return difference;
}

Check out the whole working example

Vlad Holubiev
  • 4,876
  • 7
  • 44
  • 59
  • 4
    one thing to note is that this will lose any entries where only the values change and thus this is a diff on the keys – William Reed Feb 19 '19 at 20:23
7

If I understood well you are trying to calculate symmetric difference beetween the two maps entry sets.

Map<String, Object> map1;
Map<String, Object> map2;

Set<Entry<String, Object>> diff12 = new HashSet<Entry<String, Object>>(map1.entrySet());
Set<Entry<String, Object>> diff21 = new HashSet<Entry<String, Object>>(map2.entrySet());
Set<Entry<String, Object>> result;

diff12.removeAll(map2.entrySet());
diff21.removeAll(map1.entrySet());
diff12.addAll(diff21);

Considering the awkward behavior you mentioned, let's take a closer look at the above code behavior. For example if we take the numerical example from the above given link:

Map<String, Object> map1 = new HashMap<String, Object>();
map1.put("a", 1);
map1.put("b", 2);
map1.put("c", 3);
map1.put("d", 4);

Map<String, Object> map2 = new HashMap<String, Object>();
map2.put("a", 1);    
map2.put("d", 4);
map2.put("e", 5);

After you calculate the difference as shown, the output:

System.out.println(Arrays.deepToString(diff12.toArray()));

gives:

[e=5, c=3, b=2]

which is the correct result. But, if we do it like this:

public class CustomInteger {
    public int val;

    public CustomInteger(int val) {
        this.val = val;
    }

    @Override
    public String toString() {
        return String.valueOf(val);
    }        
}   

map1.put("a", new CustomInteger(1));
map1.put("b", new CustomInteger(2));
map1.put("c", new CustomInteger(3));
map1.put("d", new CustomInteger(4));

map2.put("a", new CustomInteger(1));    
map2.put("d", new CustomInteger(4));
map2.put("e", new CustomInteger(5));

the same algorithm gives the following output:

[e=5, a=1, d=4, d=4, b=2, a=1, c=3]

which is not correct (and might be described as awkward :) )

In the first example the map is filled with int values wich are automatically boxed to Integer values.

The class Integer has its own implementation of equals and hashCode methods.

The class CustomInteger does not implement these methods so it inherits them from the omnipresent Object class.

The API doc for the removeAll method from the Set interface says the following:

Removes from this set all of its elements that are contained in the specified collection (optional operation). If the specified collection is also a set, this operation effectively modifies this set so that its value is the asymmetric set difference of the two sets.

In order to determine which elements are contained in both collections, the removeAll method uses the equals method of the collection element.

And that's the catch: Integer's equals method returns true if the two numeric values are the same, while Object's equals method will return true only if it is the same object, e.g. :

Integer a = 1; //autoboxing
Integer b = new Integer(1);
Integer c = 2;

a.equals(b); //  true
a.equals(c); //  false

CustomInteger d = new CustomInteger(1);
CustomInteger e = new CustomInteger(1);
CustomInteger f = new CustomInteger(2);

d.equals(e); //false
d.equals(f) // false

d.val == e.val //true
d.val == f.val //false

If it's still a bit fuzzy I strongly suggest reading the following tutorials:

linski
  • 5,046
  • 3
  • 22
  • 35
4
    Set<Entry<String, Object>> diff = new HashSet<Entry<String, Object>>((map1.entrySet()));
    diff.addAll(map2.entrySet());//Union
    Set<Entry<String, Object>> tmp = new HashSet<Entry<String, Object>>((map1.entrySet()));
    tmp.retainAll(map2.entrySet());//Intersection
    diff.removeAll(tmp);//Diff
Amit Deshpande
  • 19,001
  • 4
  • 46
  • 72
  • 1
    Answer doesn't look correct. Map1 could contain map2 or map2 can contain map1, or be equal or difference in any direction can exist. – user710818 Oct 04 '12 at 06:53
1

Building on Vlad's example to work with maps of different sizes

public static <K, V> Map<K, V> mapDiff(Map<? extends K, ? extends V> left, Map<? extends K, ? extends V> right) {
        Map<K, V> difference = new HashMap<>();
        difference.putAll(left);
        difference.putAll(right);

        difference.entrySet().removeAll(left.size() <= right.size() ? left.entrySet() : right.entrySet());

        return difference;
    }
Community
  • 1
  • 1
Baker
  • 24,730
  • 11
  • 100
  • 106
0

Try using guava's MapDifference.

Emil
  • 13,577
  • 18
  • 69
  • 108
0

Simple way to do it. if you want complex way, you can change filter to compare value.

    Map<String, Object> map1 = new HashMap<String, Object>() {{
        put("A", "1");
        put("B", "2");
        put("C", "3");
    }};
    Map<String, Object> map2 = new HashMap<String, Object>() {{
        put("A", "1");
        put("B", "2");
        put("D", "3");
    }};
    Map<String, Object> newList = map1.keySet().stream().filter(str -> !map2.containsKey(str)).collect(Collectors.toMap(v -> v, v -> map1.get(v)));
    Map<String, Object> oldList = map2.keySet().stream().filter(str -> !map1.containsKey(str)).collect(Collectors.toMap(v -> v, v -> map2.get(v)));

    System.out.println(newList);
    System.out.println(oldList);
Zheng Xiaodong
  • 143
  • 3
  • 9