The real answer to
why is there a
Comparator interface but no
is, quote courtesy of Josh Bloch:
The original Java APIs were done very quickly under a tight deadline to meet a closing market window. The original Java team did an incredible job, but not all of the APIs are perfect.
The problem lies solely in Java's history, as with other similar matters, e.g.
it's for historical reasons mainly; current behaviour/abstraction was introduced in JDK 1.0 and wasn't fixed later on because it was virtually impossible to do so with maintaining backward code compatibility.
First, let's sum up a couple of well-known Java facts:
- Java, from the very beginning to the present day, was proudly backwards-compatible, requiring legacy APIs to be still supported in newer versions,
- as such, almost each and every language construct introduced with JDK 1.0 lived to present day,
.equals() were implemented in JDK 1.0, (Hashtable)
Comparator was introduced in JDK 1.2 (Comparable),
Now, it follows:
- it was virtually impossible & senseless to retrofit
.equals() to distinct interfaces while still maintaining backwards compatibility after the people realized there are better abstractions than putting them in superobject, because e.g. each and every one Java programmer by 1.2 knew that every
Object has them, and they had to stay there physically to provide compiled code (JVM) compatibility also - and adding an explicit interface to every
Object subclass that really implemented them would make this mess equal (sic!) to
Clonable one (Bloch discusses why Cloneable sucks, also discussed in e.g. EJ 2nd and many other places, including SO),
- they just left them there for the future generation to have a constant source of WTFs.
Now, you may ask "what does
Hashtable have with all this"?
The answer is:
equals() contract and not-so-good language design skills of core Java developers in 1995/1996.
Quote from Java 1.0 Language Spec, dated 1996 - 4.3.2 The Class
hashCode are declared for the benefit of hashtables
java.util.Hashtable (§21.7). The method equals defines a notion
of object equality, which is based on value, not reference, comparison.
(note this exact statement has been changed in later versions, to say, quote:
The method hashCode is very useful, together with the method equals, in hashtables such as java.util.HashMap., making it impossible to make the direct
equals connection without reading historical JLS!)
Java team decided they wanted a good dictionary-style collection, and they created
Hashtable (good idea so far), but they wanted the programmer to be able to use it with as little code/learning curve as possible (oops! trouble incoming!) - and, since there was no generics yet [it's JDK 1.0 after all], that would mean that either every
Object put into
Hashtable would have to explicitly implement some interface (and interfaces were still just in their inception back then... no
Comparable yet even!), making this a deterrent to use it for many - or
Object would have to implicitly implement some hashing method.
Obviously, they went with solution 2, for the reasons outlined above. Yup, now we know they were wrong. ... it's easy to be smart in hindsight. chuckle
hashCode() requires that every object having it has to have a distinct
equals() method - so it was quite obvious that
equals() had to be put in
Object as well.
Since the default implementations of those methods on valid
Objects are essentially useless by being redundant (making
a.equals(b) equal to
a.hashCode() == b.hashCode() roughly equal to
a==b also, unless
equals is overriden, or you GC hundreds of thousands of
Objects during the lifecycle of your application1), it's safe to say they were provided mainly as a backup measure and for usage convenience. This is exactly how we get to the well-known fact that always override both
.hashCode() if you intend on actually comparing the objects or hash-storing them. Overriding only one of them without the other is a good way to screw your code (by wicked compare results or insanely high bucket collision values) - and getting your head around it is a source of constant confusion & errors for beginners (search SO to see it for yourself) and constant nuisance to more seasoned ones.
Also, note that although C# deals with equals & hashcode in a bit better way, Eric Lippert himself states that they did almost the same mistake with C# that Sun did with Java years before C#'s inception:
But why should it be the case that every object should be able to hash itself for insertion into a hash table? Seems like an odd thing to require every object to be able to do. I think if we were redesigning the type system from scratch today, hashing might be done differently, perhaps with an
IHashable interface. But when the CLR type system was designed there were no generic types and therefore a general-purpose hash table needed to be able to store any object.
Object#hashCode can still collide, but it takes a bit of effort to do that, see: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6809470 and linked bug reports for details; https://stackoverflow.com/questions/1381060/hashcode-uniqueness/1381114#1381114 covers this subject more in-depth.