Write reliable Apache Groovy code: Know your truth values
In Groovy, the whole business of “true or false” merits some discussion. Understanding it can help you avoid unexpected behavior, write clear and concise code, debug effectively and leverage Groovy’s features effectively. (If you haven’t installed Groovy yet, please read the intro to this series.)
Java programmers are — or should be — aware that equality works differently for primitive types, like int or double, than it does for instances of classes.
Consider the following Java program:
1 import java.lang.*;
2 public class Groovy06 {
3 static public void main(String[] args) {
4 String s1 = "Hi there";
5 String s2 = "Hi " + "there";
6 System.out.println("s1.equals(s2) " + s1.equals(s2));
7 System.out.println("s1 == s2 " + (s1 == s2));
8 int i1 = 2;
9 int i2 = 1 + 1;
10 System.out.println("i1.equals(i2) " + i1.equals(i2));
11 System.out.println("i1 == i2 " + (i1 == i2));
12 }
13 }
When you try to compile this, you get this error:
$ javac Groovy06.java
Groovy06.java:15: error: int cannot be dereferenced
System.out.println("i1.equals(i2) " + i1.equals(i2));
^
1 error
$
The error is telling you that, since i1 is a primitive type (int in this case) you can’t obtain a reference to it to access its equals() method – that’s the nature of a primitive type. If you change i1 and i2 to be of type Integer:
1 import java.lang.*;
2 public class Groovy06 {
3 static public void main(String[] args) {
4 String s1 = "Hi there";
5 String s2 = "Hi " + "there";
6 System.out.println("s1.equals(s2) " + s1.equals(s2));
7 System.out.println("s1 == s2 " + (s1 == s2));
8 Integer i1 = 2;
9 Integer i2 = 1 + 1;
10 System.out.println("i1.equals(i2) " + i1.equals(i2));
11 System.out.println("i1 == i2 " + (i1 == i2));
12 }
13 }
Now you can compile this program just fine. Running it yields:
$ java Groovy06
s1.equals(s2) true
s1 == s2 true
i1.equals(i2) true
i1 == i2 true
$
So what’s the difference between .equals() and ==? Make one more change to see this result:
1 import java.lang.*;
2 public class Groovy06 {
3 static public void main(String[] args) {
4 String s1 = new String("Hi there");
5 String s2 = new String("Hi " + "there");
6 System.out.println("s1.equals(s2) " + s1.equals(s2));
7 System.out.println("s1 == s2 " + (s1 == s2));
8 Integer i1 = new Integer(2);
9 Integer i2 = new Integer(1 + 1);
10 System.out.println("i1.equals(i2) " + i1.equals(i2));
11 System.out.println("i1 == i2 " + (i1 == i2));
12 }
13 }
Now run it:
$ java Groovy06
s1.equals(s2) true
s1 == s2 false
i1.equals(i2) true
i1 == i2 false
$
Here you can see that creating new instances meant that the values of each instance were equal but the instances themselves weren’t. More precisely, s1 and s2 refer to different objects, as do i1 and i2. It just so happens that s1 and s2 contain the same value, as do i1 and i2. So .equals() compares the value whereas == compares the reference, which in this case points to different parts of memory.
I’ve heard it said that subtle bugs are out there in the wilds of Java programs where coders have used == instead of .equals(). I can believe that.
The designers of Groovy took a different approach. In the Groovy world, the == symbol is precisely equivalent to the .equals() method. In the case where references must be compared, Groovy has the symbol === (and the symbol !== for negation of equality of references).
You can write a small script to test this out, based on the previous Java example:
1 String s1 = new String("Hi there")
2 String s2 = new String("Hi " + "there")
3 println "s1.equals(s2) ${s1.equals(s2)}"
4 println "s1 == s2 ${s1 == s2}"
5 println "s1 === s2 ${s1 === s2}"
6 int i1 = new Integer(2)
7 int i2 = new Integer(1 + 1)
8 println "i1.equals(i2) ${i1.equals(i2)}"
9 println "i1 == i2 ${i1 == i2}"
10 println "i1 === i2 ${i1 === i2}"
Now run it:
$ groovy Groovy06.groovy
s1.equals(s2) true
s1 == s2 true
s1 === s2 false
i1.equals(i2) true
i1 == i2 true
i1 === i2 true
$
I really like Groovy’s use of the === operator for testing the equality of references and the way == evaluates as .equals(). This feels safer to me. It’s worth emphasizing here that there aren’t any primitive types in Groovy. So having == and .equals() being different would mean that you have to remember to use .equals() on what look like primitives.
It’s also worth emphasizing that using Groovy to compile and run the Java program gives different answers than Java. This is important to remember for people moving Java code over to Groovy (or vice versa!)
So far, barring the difference between == in Java and Groovy, true and false look pretty similar. But Groovy has introduced a concept referred to as “the Groovy truth,” which doesn’t exist in Java but has some resemblance to the C language.
In Groovy, null, blank, zero or empty values evaluate to false:
1 def n = null
2 def b = ""
3 def z = 0
4 def el = []
5 def em = [:]
6 if (n == null)
7 println 'n == null'
8 if (!n)
9 println '!n'
10 if (b == "")
11 println 'b == ""'
12 if (!b)
13 println '!b'
14 if (z == 0)
15 println 'z == 0'
16 if (!z)
17 println '!z'
18 if (el == [])
19 println 'el == []'
20 if (!el)
21 println '!el'
22 if (em == [:])
23 println 'em == [:]'
24 if (!em)
25 println '!em'
Running it:
$ groovy Groovy06b.groovy
n == null
!n
b == “”
!b
z == 0
!z
el == []
!el
em == [:]
!em
$
Note this Groovy truth can appear in places other than the if statement. For example:
1 boolean x = ""
2 println x
Now run:
$groovy Groovy06c.groovy
false
$
To be clear, the assignment of “” to x is not converting “” to boolean!
Conclusion
There are three key takeaways here: First, the equivalence of == and .equals(), second the new === reference equality check and third, the Groovy truth.
