Groovy different results on using equals() and == on a GStringImpl
GroovyOperator OverloadingEqualsGstringGroovy Problem Overview
According to the Groovy docs, the ==
is just a "clever" equals()
as it also takes care of avoiding NullPointerException
:
> Java’s ==
is actually Groovy’s is()
method, and Groovy’s ==
is a clever equals()
!
>
> [...]
>
> But to do the usual equals()
comparison, you should prefer Groovy’s ==
, as it also takes care of avoiding NullPointerException
, independently of whether the left or right is null
or not.
So, the ==
and equals()
should return the same value if the objects are not null. However, I'm getting unexpected results on executing the following script:
println "${'test'}" == 'test'
println "${'test'}".equals('test')
The output that I'm getting is:
true
false
Is this a known bug related to GStringImpl
or something that I'm missing?
Groovy Solutions
Solution 1 - Groovy
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.
Solution 2 - Groovy
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 to a.compareTo(b) == 0
, when evaluating equality for Comparable
objects, and a.equals(b)
otherwise. To check for identity (reference equality), use the is
method: a.is(b)
. From Groovy 3, you can also use the ===
operator (or negated version): a === b
(or c !== 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.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() |
Solution 3 - Groovy
Leaving this here as an additional answer, so it can be found easily for Groovy beginners. I am explicitly transforming the GString to a normal String before comparing it.
println "${'test'}".equals("test");
println "${'test'}".toString().equals("test");
results in
false
true