Groovy different results on using equals() and == on a GStringImpl
Nice question, the surprising thing about the code above is that
println "${'test'}".equals('test')
returns false
. The other line of code returns the expected result, so let's forget about that.
Summary
"${'test'}".equals('test')
The object that equals
is called on is of type GStringImpl
whereas 'test'
is of type String
, so they are not considered equal.
But Why?
Obviously the GStringImpl
implementation of equals
could have been written such that when it is passed a String
that contain the same characters as this
, it returns true. Prima facie, this seems like a reasonable thing to do.
I'm guessing that the reason it wasn't written this way is because it would violate the equals
contract, which states that:
It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
The implementation of String.equals(Object other)
will always return false when passed a GSStringImpl
, so if GStringImpl.equals(Object other)
returns true when passed any String
, it would be in violation of the symmetric requirement.
In groovy a == b
checks first for a compareTo
method and uses a.compareTo(b) == 0
if a compareTo
method exists. Otherwise it will use equals
.
Since Strings and GStrings implement Comparable
there is a compareTo
method available.
The following prints true, as expected:
println "${'test'}".compareTo('test') == 0
The behaviour of ==
is documented in the Groovy Language Documentation:
In Java
==
means equality of primitive types or identity for objects. In Groovy==
means equality in all cases. It translates toa.compareTo(b) == 0
, when evaluating equality forComparable
objects, anda.equals(b)
otherwise. To check for identity (reference equality), use theis
method:a.is(b)
. From Groovy 3, you can also use the===
operator (or negated version):a === b
(orc !== d
).
The full list of operators are provided in the Groovy Language Documentation for operator overloading:
Operator | Method |
---|---|
+ |
a.plus(b) |
- |
a.minus(b) |
* |
a.multiply(b) |
/ |
a.div(b) |
% |
a.mod(b) |
** |
a.power(b) |
| |
a.or(b) |
& |
a.and(b) |
^ |
a.xor(b) |
as |
a.asType(b) |
a() |
a.call() |
a[b] |
a.getAt(b) |
a[b] = c |
a.putAt(b, c) |
a in b |
b.isCase(a) |
<< |
a.leftShift(b) |
>> |
a.rightShift(b) |
>>> |
a.rightShiftUnsigned(b) |
++ |
a.next() |
-- |
a.previous() |
+a |
a.positive() |
-a |
a.negative() |
~a |
a.bitwiseNegate() |