Groovy different results on using equals() and == on a GStringImpl
Asked Answered
C

3

65

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?

Creepie answered 13/3, 2012 at 10:24 Comment(0)
R
78

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.

Rogers answered 13/3, 2012 at 10:52 Comment(1)
An important note: the implementation of Collection.contains and the "in" operator will use the equals method and not the compareTo. This can get hard to spot when it is buried underneath a few layers ['test'].contains("${'test'}") ==> falseUlceration
W
60

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.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()
Withe answered 13/3, 2012 at 10:44 Comment(5)
Thanks for your answer. It was very helpful indeed! One thing that I noticed was that == uses equals or compareTo, however != uses equals only. Isn't it a bit counterintuitive?Creepie
Definitely. I've also added, an additional bit about == not being symmetric in groovy.Withe
Made a mistake. == is symmetric in groovy.Withe
While that table is super handy, it's a bit misleading as == isn't actually on it.Lessen
Linked to relevant part of the documentation now.Withe
D
1

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
Dedededen answered 31/3, 2022 at 14:18 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Maroon

© 2022 - 2024 — McMap. All rights reserved.