Hashing It Out
Jun. 29th, 2014 02:27 pmThinking some more about yesterday's Java problem:
In almost every case, for an "ID"-type class, two different instances of the ID that contain the same identifiers should return true when passed to the equals() method and should return identical hash code values when the hashCode() method is called. The problem is that the standard equals() and hashCode() methods that are inherited from the Object class will treat these as different objects. If an ID is supposed to uniquely identify some Object (for example, a PersonID that uniquely identifies a Person), only one instance of the ID will work to identify the unique Object if the ID is used as a key in a HashMap or the like, because a different instance of the ID, although conceptually equal, will generate a different hash code and will return false in an equals() comparison.
The solution, then, is to override the equals() and hashCode() methods for the ID classes so that IDs that are conceptually equal will return identical hash code values and will return true in an equals() comparison. This is, in fact, the case by default if the ID is simply a String.
So let's override the hashCode() and equals() methods for the ID classes. Life is good.
Except now there's no way to identify different instances of your ID object. And when you're writing a serialized stream that may contain duplicate references to the same object, you want to know if you've written an object to the stream before, so you can write the object once and restore the duplicate references to the object when reading the serialized stream back into memory. Normally, you could use a HashMap with the object as a key to keep track of this, but now two different instances of the ID will return the same hash code and will return true for equals(), so different instances are interpreted as being the same instance.
It looks like the right answer to this is to define an interface that all of the serializable objects can implement that will, by default, call back to the default object implementations of hashCode() and equals() (let's call them objectHashCode() and objectEquals()); then implement an ObjectHashMap that will call objectHashCode() and objectEquals() instead of hashCode() and equals().
*sigh*
This is an annoying amount of work to do...
In almost every case, for an "ID"-type class, two different instances of the ID that contain the same identifiers should return true when passed to the equals() method and should return identical hash code values when the hashCode() method is called. The problem is that the standard equals() and hashCode() methods that are inherited from the Object class will treat these as different objects. If an ID is supposed to uniquely identify some Object (for example, a PersonID that uniquely identifies a Person), only one instance of the ID will work to identify the unique Object if the ID is used as a key in a HashMap or the like, because a different instance of the ID, although conceptually equal, will generate a different hash code and will return false in an equals() comparison.
The solution, then, is to override the equals() and hashCode() methods for the ID classes so that IDs that are conceptually equal will return identical hash code values and will return true in an equals() comparison. This is, in fact, the case by default if the ID is simply a String.
So let's override the hashCode() and equals() methods for the ID classes. Life is good.
Except now there's no way to identify different instances of your ID object. And when you're writing a serialized stream that may contain duplicate references to the same object, you want to know if you've written an object to the stream before, so you can write the object once and restore the duplicate references to the object when reading the serialized stream back into memory. Normally, you could use a HashMap with the object as a key to keep track of this, but now two different instances of the ID will return the same hash code and will return true for equals(), so different instances are interpreted as being the same instance.
It looks like the right answer to this is to define an interface that all of the serializable objects can implement that will, by default, call back to the default object implementations of hashCode() and equals() (let's call them objectHashCode() and objectEquals()); then implement an ObjectHashMap that will call objectHashCode() and objectEquals() instead of hashCode() and equals().
*sigh*
This is an annoying amount of work to do...
no subject
Date: 2014-06-29 08:12 pm (UTC)The obvious downside is that it's a little more overhead to create and garbage-collect the wrappers. This may or may not be a deal-breaker in your application.
no subject
Date: 2014-06-30 04:19 am (UTC)You learn something new every day, I guess. :)
no subject
Date: 2014-06-29 08:20 pm (UTC)no subject
Date: 2014-06-30 04:20 am (UTC)no subject
Date: 2014-06-29 09:38 pm (UTC)no subject
Date: 2014-06-29 09:58 pm (UTC)And IdentityMap exactly solves the problem that I'm trying to solve. Now I can override the equals() and hashCode() methods for the ID classes and still handle the one place where I need to do the identity testing.
Thanks!
no subject
Date: 2014-06-29 10:13 pm (UTC)no subject
Date: 2014-06-30 10:37 am (UTC)Of course, Java 7's IdentityHashMap makes the point moot in this case.
no subject
Date: 2014-06-30 12:40 pm (UTC)And the point is only moot once I manage to get this project up onto Java 7. This is proving challenging -- not because of Java itself, but because all of the other projects that we're working with are still on Java 6.
no subject
Date: 2014-06-29 09:42 pm (UTC)To make this work, put a hash table in your class and use a factory method instead of new.
no subject
Date: 2014-06-29 09:56 pm (UTC)no subject
Date: 2014-06-29 10:12 pm (UTC)You'll also want to check out Lombok and findbugs -- the latter is kind of a super-lint for Java.
no subject
Date: 2014-06-29 10:14 pm (UTC)It's just frustrating, because I had been googling madly looking for just this feature and my google-fu failed completely. :)