# Proposal: Sameness operators

Reinier Zwitserloot reinier at zwitserloot.com
Thu Apr 2 09:15:22 PDT 2009

```The argument that .compareTo should not be used because it isn't
entirely congruent with either the meaning of .equals() or the
meanings of all the comparison operators (from == to <) on the
primitives, just doesn't hold water, for this simple reason:

They make absolutely no sense now, either, and nobody cares about that.

Here's some fun facts (all asserts are true):

int x = 0/0; //ArithmeticException
double x = 0/0.0; //okay, x = NaN
double y = 0/0.0;

assert x != y;
assert ! (x == y);
assert Double.valueOf(x).equals(Double.valueOf(y)); //WTF!
assert Double.valueOf(x).equals(y); //WTF!

Clearly, equals is fundamentally broken. We need a new equals! Oh no!

assert ! (x < y);
assert ! (x <= y);
assert Double.valueOf(x).compareTo(y) == 0; //WTF!

So, compareTo is broken as well. Just like equals is.

assert x < "foo"; //Compiler Error: Cant compare those two.
assert Double.valueOf(x).compareTo("foo"); //With java 1.4: Runtime
error.
assert Double.valueOf(x).compareTo("foo"); //With java 1.5+: Compile
time error.

A compiler error is better, but these two results certainly are
consistent. In fact, in java 1.5+, you DO get a compiler error.

So, if x < "foo" would be rewritten to the version below that after
comparison operator support is added that uses compareTo, you get
roughly the same situation: A compiler error that tells you that the
compiler didn't expect a String there. The exact text of the error is
slightly different, but other than that, it's the same. Why is this a
problem?

The fix to me seems relatively simple:

1. Anything that is legal now needs to stay legal. I would write down
the rules for this, except they are totally nuts, as the above samples
show. So, just do whatever java 6 does for everything that would have
been legal in 6. In particular, this involves unboxing both sides if
both sides can be unboxed. And unbox one side if the other side is a
numeric primitive. Yes, this leads to crazy puzzlers, such as:

Double x = Double.valueOf(Double.NaN);
double y = 0;
if ( x != y && !(x < y) && !(x > y) ) System.out.println("This line
prints? Whoa!");

But that's what we're stuck with. Unless Lovatt's and my crazy idea of
breaking backwards compatibility with an explicit source keyword is
implemented, this craziness can't go away, we're stuck with it.

2. First unbox whatever can be unboxed, then continue to:

3. If either LHS or RHS implements Comparable, we'll use that. Note
that primitives do not implement Comparable. Comparison operations
between primitives are hardcoded into the JLS spec. If both sides do
implement Comparable, LHS wins. If only the RHS does, the order is
mathematically reversed (a > b becomes b < a). If the winning HS is
willing to take Number according to the generics bound of Comparable
(which means RAW types *DO*, as they take all objects), then numeric
primitives are boxed. If neither side implements Comparable, compiler
error. If a side does, but the type of the other side isn't compatible
with Comparable's bound, or is a non-numeric primitive (boolean - char
is actually numeric, because this didn't look quite enough like a
Salvador Dali painting yet, go figure) you get a compile-time error,
even if the other way around would have worked.

4. BigDecimal and BigInteger are changed to implement
Comparable<Number> instead. The fact that they currently don't is
something I don't really understand would consider filing as a bug if
I worked more with mixed BI/BD and primitives math. By doing this,
something like:

5 < new BigInteger("10") would work out the way you expect it to (that
would return 'true').

BI/BD's comparison methods can't just convert the incoming primitive
to a BI/BD, because NaN and the infinities don't convert, yet the

Double.NEGATIVE_INFINITY < BigInteger.ZERO

should clearly be true. The above plan can make that happen by adding
more logic to BI/BD's compareTo methods.

The reason Double and company need to unbox is because their
implementations of compareTo can then stay blissfully unaware of BI
and BD's existence. There's a problem if you attempt to compare 2
numeric entities that aren't hardcoded into the JVM, but I don't think
a perfect solution is possible here.

This proposal is not entirely consistent with the primitive's compare
operators, but attempting to be consistent with the current mess that
is comparison operators between primitives is not a goal that we
should aspire to, IMO. The concept of 'I'll give you an answer if
there is a mathematical answer, and if there isn't, I will give you a
compile-time error if I can, and a runtime error otherwise' seems
perfect to me. That's compareTo's current semantics, so, lucky us!

NB: Attempting to integrate equality into compareTo is probably a bad
idea, eventhough we are stuck with some funny puzzlers where equality
and comparison clearly aren't consistent. The real problem is that
numeric equality is just different from object equality (numerically,
NaN is not equal to NaN, but as objects, they are, and asking if apple
== pear makes no sense numerically, but as objects, there's a logical
answer: No, they aren't). I don't see us getting out of that one.
Possibly use <> as an operator that specifically means: Not
Numerically equal, and !<> as numerically equal. Yeah, that doesn't
really look too readable to me either.

--Reinier Zwitserloot

On Apr 2, 2009, at 14:48, Mark Thornton wrote:

> Tom Hawtin wrote:
>> Reinier Zwitserloot wrote:
>>
>>> Why can't we use compareTo and equals?
>>>
>>
>>  * compareTo may well be unreasonably inefficient.
>>  * equals handles nulls (asymmetrically), compareTo does not
>>  * equals does test-and-cast (event for generic types - urgh),
>> compareTo uses generics
>>  * equals does not throw ClassCastException, compareTo does (but
>> probably shouldn't with generics)
>>
>> Therefore, new methods please. I'm sure you are aware the whole