Most of the map classes in Java override AbstractMap
and use its implementation of equals
method which checks that:
contains all the entries present in this
if (o == this)
return true;
//check that passed object is of type Map
if (!(o instanceof Map))
return false;
Map<?,?> m = (Map<?,?>) o;
//check that passed object has same length
if (m.size() != size())
return false;
//passed object contains all the entries
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;
But ConcurrentHashMap uses a different implementation where instead of matching length of both the maps, the entries present in passed map are also iterated and matched.
if (o != this) {
//check that passed object is of type Map
if (!(o instanceof Map))
return false;
Map<?,?> m = (Map<?,?>) o;
Node<K,V>[] t;
int f = (t = table) == null ? 0 : t.length;
Traverser<K,V> it = new Traverser<K,V>(t, f, 0, f);
//passed object contains all the entries
for (Node<K,V> p; (p = it.advance()) != null; ) {
V val = p.val;
Object v = m.get(p.key);
if (v == null || (v != val && !v.equals(val)))
return false;
}
//this contains all the entries of the passed object
for (Map.Entry<?,?> e : m.entrySet()) {
Object mk, mv, v;
if ((mk = e.getKey()) == null ||
(mv = e.getValue()) == null ||
(v = get(mk)) == null ||
(mv != v && !mv.equals(v)))
return false;
}
}
return true;
Since equals
method is not thread safe even in ConcurrentHashMap
can someone suggest what is the benefit of skipping length check and instead iterating and matching entries from passed object?
As pointed in answers below that size is not available as a direct field, this is the equals
implementation which I believe is more efficient. Please clarify issues in this one. Mostly we are not doing any lookup in the last loop.
if (o != this) {
//check that passed object is of type Map
if (!(o instanceof Map))
return false;
Map<?,?> m = (Map<?,?>) o;
Node<K,V>[] t;
int f = (t = table) == null ? 0 : t.length;
Traverser<K,V> it = new Traverser<K,V>(t, f, 0, f);
int thisSize=0;
//passed object contains all the entries
for (Node<K,V> p; (p = it.advance()) != null; ) {
V val = p.val;
Object v = m.get(p.key);
if (v == null || (v != val && !v.equals(val)))
return false;
thisSize++;
}
//passed object is of the same size, ignoring any modifications since invocation of equals
int passedObjectSize=0;
for (Map.Entry<?,?> e : m.entrySet()) {
Object mk, mv, v;
if ((mk = e.getKey()) == null ||
(mv = e.getValue()) == null){
return false;
}
//ignore checking that get(mk) is same as mv
passedObjectSize++;
}
return thisSize==passedObjectSize;
}
return true;
Why we override equals() method? It needs to be overridden if we want to check the objects based on the property. For example, we want to check the equality of employee object by the id. Then, we need to override the equals() method.
The answer is to write your own equals method and specify what you really want to compare. Show activity on this post. It is considered good practice to override the equals method whenever a class is created and will be compared.
The reason the equals method in the Object class does reference equality is because it does not know how to do anything else. Remember, every class in Java is an Object (via inheritance).
This article explains why it's important to implement these methods correctly and then explains how to do so. Object declares three versions of the wait method, as well as the methods notify , notifyAll and getClass . These methods all are final and cannot be overridden.
I think that checking the size would be useless, when computing the size Traverser
is not used at all, it uses a specialization of LongAdder
(called CounterCell
), so it takes time to compute the size and by the time this is done - the CHM could change entirely before traversing.
Even computing the size
has no guarantees that it will be correct for example CHM
could be mutated while computing the size - so that number would not be accurate.
So I guess this can be seen as an optimization: why compute the size if most of the time it is useless anyway.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With