17

Newbie question about java HashSet

Set<User> s = new HashSet<User>();
User u = new User();
u.setName("name1");
s.add(u);
u.setName("name3");
System.out.println(s.contains(u));

Can someone explain why this code output false ? Moreover this code does not even call equals method of User. But according to the sources of HashSet and HashMap it have to call it. Method equals of User simply calls equals on user's name. Method hashCode return hashCode of user's name

skaffman
  • 398,947
  • 96
  • 818
  • 769
user12384512
  • 3,362
  • 10
  • 61
  • 97
  • To quote Jon Skeet "Objects in hashsets should either be immutable, or you need to exercise discipline in not changing them after they've been used in a hashset (or hashmap)." - http://stackoverflow.com/questions/4718009/mutable-objects-and-hashcode – Qwerky Aug 11 '11 at 13:46

2 Answers2

15

If the hash code method is based on the name field, and you then change it after adding the object, then the second contains check will use the new hash value, and won't find the object you were looking for. This is because HashSets first search by hash code, so they won't bother calling equals if that search fails.

The only way this would work is if you hadn't overridden equals (and so the default reference equality was used) and you got lucky and the hash codes of the two objects were equal. But this is a really unlikely scenario, and you shouldn't rely on it.

In general, you should never update an object after you have added it to a HashSet if that change will also change its hashcode.

dlev
  • 48,024
  • 5
  • 125
  • 132
  • so it's a copy of u added in the hashset ? Otherwise I'd think the object in nameset would have name3 aswell – Ced Feb 05 '16 at 18:04
11

Since your new User has a different hashcode, the HashSet knows that it isn't equal.

HashSets store their items according to their hashcodes.
The HashSet will only call equals if it finds an item with the same hashcode, to make sure that the two items are actually equal (as opposed to a hash collision)

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • 1
    Indeed it calls equals only if hashCode are equal. This means that if i update User, which change it hashCode, the nested array that contains Entry associated with their hashCodes would not be updated. So iterating over this array would not return entry with same hashCode. Should work if hashCode always return 0 ) – user12384512 Aug 11 '11 at 13:39
  • 3
    Correct. In general, it is a bad idea to put mutable objects in a HashSet. If you make `hashCode()` return `0`, you will lose all of the performance benefits of the HashSet, and you'll end up with the slowest collection you can possibly get. – SLaks Aug 11 '11 at 13:41
  • I know, this is just an example – user12384512 Aug 11 '11 at 13:44
  • I believe this should be the accepted answer. It's worth noting that the javadoc for `HashSet.contains()` does not say anything about equals being applied only after the check for hashcode. – Felipe Leão Oct 24 '17 at 17:34