Why are these two exceptions necessary to "allow hash tables to operate properly"?
In fact, that statement is a bit misleading. It would be more accurate to say that those exceptions in the definition of Double.equals()
are necessary for the chosen implementation of Double.hashCode()
to be consistent with equals()
. That characteristic is indeed relevant to the Java platform library's hash implementations. You'll find a great deal of verbiage devoted to that topic generally, both on SO and elsewhere. For example:
Since the general topic of hashCode()
/ equals()
consistency is so well covered, I'll focus on how they apply to class Double
. There are several details in this area that it is necessary to understand:
- Java
Double
is the wrapper class for double
, and double
is defined in terms of IEEE-754 binary double precision format.
- In IEEE-754, positive and negative zero are distinct values. Even though they compare equal to each other, they have different bit patterns, and they are distinguishable by some of their other properties. This is useful and desirable for some purposes.
- On the other hand, although IEEE-754 defines several "not a number" (NaN) bit patterns, Java uses only one of them.
- IEEE-754 specifies that its special NaN values compare unequal to every value, including themselves. This is one of their distinguishing features.
Double.hashCode()
is defined in terms of arithmetic operations on the bit pattern of the wrapped double
.
Because Double.hashCode()
is computed from the wrapped double
's bit pattern, and the bit patterns of positive and negative zero differ, the hash codes of Double(+0.0)
and Double(-0.0)
differ. That would make this hashCode()
implementation inconsistent with equals()
if two such Double
instances compared equal. Therefore, Double.equals()
is defined so that they do not compare equal. That was not the only alternative: hashCode()
could have instead been defined so that the two flavors of zero had the same hash code.
On the flip side, because Java provides only one NaN value of type double
, with its specific bit pattern, Double
instances representing that value yield the same hash code. Although again this follows from the chosen implementation of hashCode()
, there would be no easy way to mirror IEEE-754 equality semantics in class Double
, because doing so would need to violate an even more important invariant than equals()
being consistent with hashCode()
: equals being reflexive. That is, it is expected always to be the case that for any non-null reference a
, a.equals(a)
is true.