Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Value found from Map not consistent with identical hashcodes and equals

Maybe not the best Title, please feel free to edit it.

This is the code I have..

import java.util.Map;
import java.util.HashMap;

public class App {
    public static void main(String[] args) {
        final Foo foo = new Foo();
        final Foo bar = new Foo();

        System.out.println("foo equals foo: " + foo.equals(foo));
        System.out.println("foo equals bar: " + foo.equals(bar));

        System.out.println("foo hashcode: " + foo.hashCode());
        System.out.println("bar hashcode: " + bar.hashCode());

        final Map<Foo, Integer> foos = new HashMap<Foo, Integer>();
        foos.put(foo, -99);

        System.out.println("foos.getfoo: " + foos.get(foo));
        System.out.println("foos.getbar: " + foos.get(bar));
    }    
}

class Foo {
    @Override
    public boolean equals(Object o) {
        return false;
    }

    @Override
    public int hashCode() {
        return -1;
    }
}

So before reading further can you guess what the output for the following 2 statement will be?

System.out.println("foos.getfoo: " + foos.get(foo));
System.out.println("foos.getbar: " + foos.get(bar));

I would expect to see:

null
null

since even though the hashCodes do match, equals for any instance of Foo will always return false, so using an instance of Foo as a key in a Map should not be useful at all..

However the output is:

:~ $ javac App.java
:~ $ java App
foo equals foo: false
foo equals bar: false
foo hashcode: -1
bar hashcode: -1
foos.getfoo: -99
foos.getbar: null

What am I missing? How is -99 retrieved when I use an object that has an hashcode -1 and is NOT equal to itself, but then get null later with a same type of instance that is also NOT equal to what I have in the Map and also has hashcode -1?

like image 762
Koray Tugay Avatar asked Mar 09 '23 11:03

Koray Tugay


1 Answers

Because the get() method of HashMap is optimized to check first the object reference equality before looking equals() :

Look at the Node<K,V> getNode(int hash, Object key) method that is invoked by the V get(Object key) method :

final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        if ((e = first.next) != null) {
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

This :

(k = first.key) == key

and

(k = e.key) == key

refer to this optimization.


Besides, here, you violate the rule of the equals() contract that says that is has to be reflexive :

class Foo {
    @Override
    public boolean equals(Object o) {
        return false;
    }    
   ...
}

for any non-null reference value x, x.equals(x) should return true.

From the moment where a class violates the equals() contract, you cannot have any guarantee that the classes that manipulate instances of the flawed class will have the expected behavior.

like image 176
davidxxx Avatar answered Apr 26 '23 16:04

davidxxx