Win a copy of Kotlin in Action this week in the Kotlin forum!
  • Post Reply Bookmark Topic Watch Topic
  • New Topic

How to handle hashCode() implementation , when using non-final variables for equals?  RSS feed

 
Claude Sylvanshine
Greenhorn
Posts: 22
1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hello, i guess its a simple question, but for some reason all answers i see online aren't exactly clear enough , or go too deep into language semantics.

So basically if i have the following class



I want to be able to compare instances of this class based on the state of storage. It works , but i have a warning that i am not changing hashCode(). Ok, but as i look into the contract for hashCode() , it says it should not be changing throughout the life of the instance and if equals returns true, this means that the two instances have the same hashCode as well. How can i achieve this in a scenario when the variables used in equals are potentially changing?

Is manually adding a specific attribute to serve this purpose only( and additionally be used in HashMaps and whatnot, if needed, as it will not be identical between all instances) a good idea? Like this



(additional question)
Is the cast to the raw variant of CustomClass ok ? It removes the warnings that i get if i cast using CustomClass<E> and serves the same purpose. If its not , why,  and is there an alternative for casting the Object input ?.
 
Junilu Lacar
Sheriff
Posts: 11125
160
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Claude Sylvanshine wrote:as i look into the contract for hashCode() , it says it should not be changing throughout the life of the instance


I see nothing in the documentation that says that. Can you give us a reference to where you are getting this information? Or maybe you simply misunderstood this part:
Java API for Object.hashCode() wrote: the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified.


That doesn't say what you seem to think it says. All that means is that as long as there are no changes to the properties on which equals() comparisons are made, hashCode() should return the same integer.

hashCode() should be calculated dynamically if it's based on mutable (can change) fields. So, whenever a mutable field of an object is changed, its hashCode should potentially also change. I say potentially because two objects that are NOT equal() can actually have the same hashCode.

The only time it's safe to use a static, once-only calculated hash code is when it is based on immutable properties of the object and equals() is based on the exact same set of properties as well.
 
Claude Sylvanshine
Greenhorn
Posts: 22
1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
This is from the OCP - Study Guide by Boyarsky and Selikoff . 

The official JavaDoc contract for hashCode() is harder to read than it needs to be. The
three points in the contract boil down to these:
■ Within the same program, the result of hashCode() must not change. This means that
you shouldn’t include variables that change in figuring out the hash code. In our hippo
example, including the name is fine. Including the weight is not because hippos change
weight regularly.
■ If equals() returns true when called with two objects, calling hashCode() on each of
those objects must return the same result. This means hashCode() can use a subset of
the variables that equals() uses. You saw this in the card example. We used only one
of the variables to determine the hash code.
■ If equals() returns false when called with two objects, calling hashCode() on each of
those objects does not have to return a different result. This means hashCode() results
do not need to be unique when called on unequal objects


Its not stated as mandatory obviously, but i assumed it is pretty  much expected to behave like that.  Its a fact that your quote from the API is not as strict as this . Or is my English failing me somehow?
 
Paul Clapham
Sheriff
Posts: 22472
43
Eclipse IDE Firefox Browser MySQL Database
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Let me try and turn all of that stuff inside out, and see if that clarifies things.

First of all: if two objects are equal then they must have the same hashcode. That's the only "must" you need to look at for equals and hashcode. If two objects aren't equal then there is no rule about their hashcodes.

So, let's suppose you have two objects which are not equal. Then it's possible (maybe even likely) that they have different hashcodes. Let's suppose for now that the hashcodes are different.

Now suppose you change some mutable fields in the objects and the result is that they are now equal. This means that their hashcodes must now be equal, right? But before we changed those fields their hashcodes were different. So it follows that changing the mutable fields should result in changing the hashcodes so that the one and only "must" is obeyed.

Now you've seen people (and quotes) in this thread which says that hashcodes must not change. That's a slightly different kind of "must". Here's the reason for it: Let's suppose you add an object to a Map and the object's hashcode is, say, 2371. And then let's suppose something causes the object's hashcode to 447. Then looking for the object in the Map is likely to fail because the Map put it in there with hashcode 2371 and will look for it based on that. And that is a Bad Thing™.

Moral of the story: Having two objects for which the equals() method sometimes returns true and other times false, in the same run of an application, can cause problems if you need to put those objects into a Map. If you're not going to put them into a Map then the hashcode value becomes irrelevant. But if you're going to do that weird thing with the equals() method you better be sure that you plan to never put the objects into a Map.
 
Piet Souris
Rancher
Posts: 1979
67
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Why don't you base your hashcode solely on the id field? That's why you have that field. To promise never to use your objects in a Map, well, thát would not be very reliable, me thinks.
 
Claude Sylvanshine
Greenhorn
Posts: 22
1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
@Piet , well the id field is not present in the original code( first code snippet) i have added it as a proposal in the second one ( btw is there a way to make marks and change the color of parts of the code, trying to make it bold did not work

@ Paul - thanks a lot for the explanation. I decided to look at how obviously mutable classes handle this and checked the code for AbstractList ones, that the other list collections get. Well the hashCode is changing in parallel with the contents of the list. So i tried to put one in a map , just to see for myslf and yes, it messes it up if we modify it after adding it.
Here is the code.




Ok its something i will learn to live with i guess. Thanks again everyone.
 
Piet Souris
Rancher
Posts: 1979
67
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Ah indeed, I  only looked at your second code. Sorry I missed that.
 
Paul Clapham
Sheriff
Posts: 22472
43
Eclipse IDE Firefox Browser MySQL Database
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Piet Souris wrote:To promise never to use your objects in a Map, well, thát would not be very reliable, me thinks.


No, the word for that development technique is "ticking time bomb".
 
Piet Souris
Rancher
Posts: 1979
67
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
 
Winston Gutkowski
Bartender
Posts: 10573
65
Eclipse IDE Hibernate Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Claude Sylvanshine wrote:Ok its something i will learn to live with i guess...

However, it ONLY applies to HashMaps (or any other "hashed" collection). In theory, there's no reason why you shouldn't be able to change the keys of a TreeMap ... although whether you actually can in practise is another matter.

Winston
 
Campbell Ritchie
Marshal
Posts: 55672
161
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Claude Sylvanshine wrote:. . . checked the code for AbstractList ones, that the other list collections get. . . . something i will learn to live with i guess. Thanks again everyone.
I think the idea with hash code methods and the List interface is that all Lists containing the same contents will return the same hash code. Check the List#equals() method, too. You find that two different List implementations but with the same contents will return true from equals. They must therefore return the same value from hashCode, which is why ArrayList inherits its equals and hash code methods from AbstractList. So does LinkedList, for the same reason.

You will find that Maps always have problems if you use mutable objects as keys: have a look at this:-
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.
...from the Map interface documentation. That means that you cannot usually find the key in the Map. The same applies to hash‑based Sets.
 
Paul Clapham
Sheriff
Posts: 22472
43
Eclipse IDE Firefox Browser MySQL Database
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Winston Gutkowski wrote:However, it ONLY applies to HashMaps (or any other "hashed" collection). In theory, there's no reason why you shouldn't be able to change the keys of a TreeMap ... although whether you actually can in practise is another matter.


Right, that's an issue which I glossed over. And yes, a TreeMap uses the compareTo() method of its entries rather than the hashCode() method. However if you have objects whose equals() method isn't stable, then chances are the compareTo() method isn't stable either, and then perhaps a TreeMap would have problems dealing with such objects anyway. But I haven't written the "smoking gun" test program like the one Claude wrote earlier in this thread, for that situation.
 
Campbell Ritchie
Marshal
Posts: 55672
161
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Even then the behaviour is not specified.
Imagine you have a tree set (small t and s) containing the following values 1 2 3 4. Now let us imagine that one of those objects changes its state so its value becomes 5. There is likely to be no way you will ever find it again, is there?
 
Campbell Ritchie
Marshal
Posts: 55672
161
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I tried it out; small changes which don't affect sorting order appear not to cause the K to vanish, but larger changes which do affect sorting order cause the offending K not to be found.
 
Claude Sylvanshine
Greenhorn
Posts: 22
1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hmmm , for some reason i have stopped getting email notifications for this topic.

@Campbell Ritchie
I checked the listed methods , but my main goal was to see if they are having some magic that will make them have constant hashCode  and variable equals results. Which, right now, i cant think of a case where it can be actually useful.  The need for consistency based on this rule has way more substance to it , for me personally, after this discussion.

  If equals() returns true when called with two objects, calling hashCode() on each of
those objects must return the same result.
 
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
Boost this thread!