-2

Why doesn't my Java HashMap work? My object has the property that .equals equality implies hashCode equality.

You can assume that I'm modifying a field of the object after I add the object to the HashMap.

interestedparty333
  • 1,980
  • 15
  • 28
  • as mentioned in the answer of [this question](https://stackoverflow.com/questions/27581/what-issues-should-be-considered-when-overriding-equals-and-hashcode-in-java) at the bottom: you need a "final" hash code if you're using hash based collections – Stefan Warminski Jun 15 '17 at 06:33
  • Are you using the same object as key and value of your hashmap? If so, what do you want to archive? Usually, the key should not be changed at all after adding the entry to the hashmap. – Erich Kitzmueller Jun 15 '17 at 06:33
  • The property that is required is that object equality implies hashcode equality, not the other way round. You can't modify a field of the object if the object is used as a key and the field forms part of the hashcode or equality computations. – user207421 Jun 15 '17 at 06:35
  • Where's your code? How could we know what you might be doing wrong without having the opportunity to see what you've tried so far? – fps Jun 15 '17 at 18:11
  • Eh, no code needed. Just trying to improve the situation with search results for others. I'll mark an answer as accepted so it stops showing up in your "open question" feeds. – interestedparty333 Jun 15 '17 at 22:29
  • No, code is very much required. This is a really bad question at the moment. Please read https://codeblog.jonskeet.uk/2010/08/29/writing-the-perfect-question/ (Your current question doesn't even state that one of the fields being changed is part of the equality check and hash code.) It should be a very simple matter to provide a [mcve], which would make the question much, much better. – Jon Skeet Jun 16 '17 at 17:15

2 Answers2

5

You can assume that I'm modifying a field of the object after I add the object to the HashMap.

That right there is why.

Javadoc says:

Note: great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map.

"Not specified" means "may not to work", so you should not be surprised when it doesn't work.

Andreas
  • 138,167
  • 8
  • 112
  • 195
1

If you're mutating (changing) the objects after you add them to the HashMap, your objects will be in the wrong bucket. This is because even though the object has changed, the object is not "refiled" into the (new) correct bucket after the changes occur.

Thus, methods like remove will fail to find your object, because the object is sitting in an outdated bucket.

What really drives this home (for me) is that when the object is initially added to the HashMap, the hash value (an int) is stored with the Entry. This implies that the hash is a relatively static property that is rarely (never) updated.

Possible work-arounds include:

  1. It'll probably work if you iterate through each Entry in the hashmap and create a new HashMap from those entries. This is probably bad in terms of performance but probably the most correct thing to do.

  2. Don't include the field that's changing in your hashCode function. While not great (taking advantage of unspecified compiler behavior is risky business, at best), it tends to work, because you'll just have collisions, which only affect performance, not correctness. You still need to include the problematic field in your .equals(Object obj) method, or else you can end up removing the wrong objects.

  3. Algorithmic solution: Find a way to store a different piece of data in the object that acts as the key that's constant. E.g., in my application, I was tracking the "age" of the object with an integer field. Every time I reached a certain amount of time in my application, I would go increment the age of all of the keys. If something was older than 50 units of time, then I could reasonably throw out the key-value pair. Alternatively, I could change the object to store the "birthtime" of the object. That is, if the application was 1000 time units old when the object was created, I'd store the number 1000. Then, to determine the age of the object, I could diff the current "time" against the birthtime and throw out the object if the difference was > 50.

interestedparty333
  • 1,980
  • 15
  • 28
  • Exlucing the mutable field from only the hashCode will not solve the problem. Yes the hashCode will find the correct bucket, but the retrieval logic needs to assert that it is the right object (since multiple keys may map to that bucket) and it will use equals to verify that. If equals still uses the mutable field it will fail to recognize it's the intended key. – bowmore Jun 15 '17 at 06:35
  • Since the equals includes the key in the comparison, it'll still work. I've updated my answer to more seriously reflect the trouble that taking advantage of undefined compiler behavior can get you in. – interestedparty333 Jun 15 '17 at 22:41
  • You're right, it will work, however I'm now unsure what unspecified compiler behavior you think is in play. – bowmore Jun 16 '17 at 05:06
  • Ah, I'm just referencing Andreas's quote of the Map javadoc: "The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map." – interestedparty333 Jun 16 '17 at 16:53