简体   繁体   中英

How to override equals for two Maps of <String, Object>?

I have a class that has a Map<String, Object> field (the keys are Strings, the values are Objects that have correctly implemented the "equals" method for comparison).

I would like to override equals for this class in a way that only returns true if the Maps have equal mappings between keys and values.

Here is my attempt:

// Assumes that the Object values in maps have correctly implemented the equals method.
private boolean mapsEqual(Map<String, Object> attributes) 
{
    if (this.attributes_.keySet().size() != attributes.keySet().size() ||
        this.attributes_.values().size() != attributes.values().size())
        return false;
    for (String key : attributes.keySet()) {
        if (!this.attributes_.keySet().contains(key))
            return false;
        if (!this.attributes_.get(key).equals(attributes.get(key)))
            return false;
    }
    return true;
}

However, this implementation fails when the same key is added more than once or when a key is removed from the map (the size tests fail for the values, as they count the duplicates and do not resize when values are removed.)

It seems that my situation should be common enough to find information that is relevant to my case, but I could not find any. Is there any legacy code or widely accepted solution to this situation? Any help or working solution is appreciated.

I am going to put this as an answer even though I am not 100% sure it solves your problem (but it's simply not gonna fit in a comment).

First off, to repeat my comments: The Map interface forbides that a map has duplicate keys or multiple values per key. Any proper implementation (eg java.util.HashMap ) will therefore not allow this. Typically they will just replace the value if this happens.

Furthermore, the specification for equals , to me, seems to be doing what you want. Again, a proper implementation must live up to that specification.

So, what's the point here: If you are writing your own class that is implementing Map , then it simply cannot allow duplicate keys (methods like get wouldn't make sense anymore). If you are using a built-in implementation such as HashMap , it replaces the values anyway.

Now you are saying that you're experiencing size issues with keySet() and values() . I think you should add example code that will cause this behavior. The following works just fine for me:

Map<String, String> map = new HashMap<String, String>();
map.put("Foo", "Bar");

System.out.println(map.keySet().size()); // 1
System.out.println(map.values().size()); // 1

map.put("Foo", "Baz"); // the HashMap will merely replace the old value

System.out.println(map.keySet().size()); // still 1
System.out.println(map.values().size()); // still 1

Removing a key will, of course, change the size. I don't see how you consider this a problem based on your explanations so far.

As for equals , you may just want to look at the implementation for HashMap , which can be found here :

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;
}

Consider the following example:

Map<String, String> map1 = new HashMap<String, String>();
map1.put("Foo", "Bar");

Map<String, String> map2 = new HashMap<String, String>();
map2.put("Foo", "Bar");

System.out.println(map1.equals(map2)); // true

Firstly, you complain about your maps having duplicate keys... not possible (unless you're using a badly broken implementation).

This should do it:

public boolean equals(Object o) {
    if (!(o instanceof MyClass))
        return false;
    MyClass that = (MyClass)o;
    if (map.size() != that.map.size())
        return false;
    for (Map.Entry<String, Object> entry : map) {
        Object a = entry.getValue();
        Object b = that.map.get(entry.getKey());
        if ((a == null ^ b == null) || (a == null && !a.equals(b)))
            return false;
    }
    return true;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM