Writing equals() and hashCode() implementations for a concrete or abstract class is pretty easy, but what do you do when an interface gets involved? As an example, let's say I have an Address interface and every Address has five attributes: street address, city, region, postal code and country.
A simple abstract implementation.
Now how would I approach making sure this is consistent with other implementations of Address? I'd be comfortable with just specifying in Address what equals() must fulfill (i.e. each attribute must be equal to the other using equals()) but what about the hash code? If they use a different algorithm, or the same algorithm with a different starting value or multiplier it will come out differently even if they both return true from equals().
There's a pretty good case for saying instances of two different classes should never be equals(). How different could two concrete Address classes be and still be equal?
A good question is never answered. It is not a bolt to be tightened into place but a seed to be planted and to bear more seed toward the hope of greening the landscape of the idea. John Ciardi
Joined: Jul 15, 2003
I don't have an answer to that. I suppose it's just me being over zealous in wanting to allow someone to create a different implementation that could still be the same. Suppose later someone needed a mutable address and created a MutableAddress implementation because AbstractAddress is immutable. If they still have the same street, city, region, postal code and country they're equal, yet if they have different hash code algorithms they won't work with a hash based collection.
Well, it's possible to allow subclasses to be equal to other subclasses, but it's usually inadvisable as the process is somewhat error-prone and likely to lead to confusing results. Generally in order to do this, you would need to require that all subclasses use effectively the same implementation of equals() and hashCode(). By "effectively the same" I mean that it would be preferable if they all inherited the exact same implementation from a single base class, which declares those two methods to be final. In some cases you might be tempted to make this base implementation nonfinal in order to allow subclasses to make implementations which are more efficient for that particular implementation. However the results of such methods are obligated to be the same as the base implementation results, or you will violate the base contract of equals() and/or hashCode().
As an example of a data type which allows different subtypes to evaluate as equal to each other, check out java.util.List. Since this is an interface, they can't provide a final implementation of equals() and hashCode() (and they decline to make the implementations in AbstractList final, thought they probably should have). However the APIs for these two methods in List do spell out fairly exact rules which all subtypes are required to follow, or lead to very confusing results.
Also, regarding your example of two Address types which are identical except that one is mutable while the other is immutable - personally I feel there is no good reason to use equals() or hashCode() on a mutable object if an immutable version is available. Mutable objects make lousy keys in Maps. Or rather, it's a horrible idea to ever change their values after they've been inserted into a Map. And so if an immutable version is available, why not use it? Much better I think. That, in my opinion, is why they didn't override equals() and hashCode() in StringBuffer. Why bother? Anyone wanting to evaluate equality of such objects is better off using immutable Strings instead. Not everyone agrees with this opinion of course. But still, the importance of having equivalent equals() implementations for mutable and immutable versions of what is otherwise the same datatype is... limited, at best. In my opinion.
"I'm not back." - Bill Harding, Twister
Joined: Jan 29, 2003
Maybe an equivalentAddress method would be in order. Or an AddressComparator class that could compare the address portion of different implementations. With either of those you wouldn't risk confusing the equals() hashcode() and compareTo() on the implementation classes.
Joined: Jul 15, 2003
I just marked the equals() and hashCode() methods in the abstract class as final. If somebody wants to compare addresses of different implementations later they can use a Comparator or something akin to one.