Why does equals
"never fail"?
As per Tom's comment:
.. If you modify the object in the map but keep the key constant, then you'll still be able to get the value using the same key instance. dog.equals(dog) will always be true with your code (unless there is concurrent modification)
That is, the line:
d1.name = "arthur";
Is mutating the object already in the HashMap. Compare with (where t
"prints" true or false):
Dog d1 = new Dog("clover");
// what "put" is effectively doing: there is *no* Copy/Clone
Dog inMap = d1;
t(inMap == d1); // true: same object, reference equality!
d1.name = "arthur";
t(inMap.name.equals("arthur")); // true: same object! "name" member was *mutated*
t(d1.equals(inMap)); // true: same object!
So the equals
never fails because it is comparing the object with itself :)
I missed it at first too: remember Java has Call By Object-Sharing semantics. That is, there is no implicit Copy/Clone/Duplicate for objects that are passed to methods.
So then, how to get it to fail:
Dog d1 = new Dog("clover");
Dog d2 = new Dog("clover");
t(d1 == d2); // false: different objects!
m.put(d1, "Dog key"); // put in with D1 object
System.out.println(m.get(d1)); // "Dog key" -okay, equals
System.out.println(m.get(d2)); // "Dog key" -okay, equals
d2.name = "arthur"; // *mutate* D2 object
t(d1.equals(d2)); // false: no longer equal
System.out.println(m.get(d1)); // "Dog key" -okay, always equals, as per above
System.out.println(m.get(d2)); // "" -no good, no longer equals
And how does the hashCode
fit in?
The hash code is used to determine the hash table bucket to put the key (and value pair) in. When performing a look-up (or set) the bucket is first looked up by hash code and then each key already mapped to the bucket is checked with equals
. If there are no keys in the bucket then equals is never invoked.
This explains why changing the name
to a String of length 8 in the original post results in a failing look-up: a different bucket (say, one that is empty) is initially chosen and thus equals
is never invoked upon existing keys which exist in other buckets. The same object key might be already there, but it's never looked at!
So then, how to get it to fail with different hash code:
Dog d1 = new Dog("clover");
m.put(d1, "Dog key"); // put in with D1 object, hashCode = 6
System.out.println(m.get(d1)); // "Dog key" -okay, hashCode = 6, equals
d1.name = "Magnolia"; // change value such that it changes hash code
System.out.println(m.get(d1)); // "" -fail, hashCode = 8, equals
// ^-- attaching a debugger will show d1.equals is not called
Thus, for a key to be found in a hash table (such as a HashMap) it must be such that:
k.hashCode() == inMap.hashCode() && k.equals(inMap);
There may be many hash codes that map to the same bucket. However the above is the only guarantee that a look-up will be successful.
Of course, see the other replies for correct way to compare strings in general.
x == y
, where the types of x/y are not primitive. (It does warn in C#/VS for various forms, BTW.) That is, I really wish an explicit(object)x == (object)y
form (or other) was required .. this question/issue comes up a good bit. </rant> – user166390dog.equals(dog)
will always be true with your code (unless there is concurrent modification). – Tom Hawtin - tackline