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