Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cant understand advantage of overridden implementation of equals method in ConcurrentHashMap

Most of the map classes in Java override AbstractMap and use its implementation of equals method which checks that:

  1. passed object is of type Map
  2. has same length
  3. 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;
like image 686
kunjbhai Avatar asked Jun 14 '18 12:06

kunjbhai


People also ask

What is the need for Overriding the equals() method in Java?

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.

Is it mandatory to write equals method when we write custom classes?

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.

Why is it important to write the equals () method when writing a class?

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).

Which of the following methods Cannot be overridden for an object of object class?

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.


1 Answers

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.

like image 79
Eugene Avatar answered Oct 20 '22 00:10

Eugene