III Operators
23. Introduction
Groovy supports a range of operators - those you know from primary school (e.g. + and =), through to more specialised operators.
Operators are used with values and variables and the context in which they’re used will vary the resulting output. This introduction lists the range of operators available to you and the following chapters describe each in more detail.
Arithmetic and Conditional Operators
| Operator(s) | Type |
|---|---|
= |
Simple Assignment Operator |
! |
Logical Complement Operator |
== !=
|
Equality Operators |
+ - * / % **
|
Arithmetic Operators |
> < <= >=
|
Relational Operators |
++ --
|
Increment and Decrement Operators |
&& || ?:
|
Conditional Operators |
<< >> >>> ~ & | ^
|
Bitwise Operators |
+= -= *= /= &= |= ^= %= <<= >>= >>>=
|
The Compound Assignment Operators |
String Operators
| Operator(s) | Type |
|---|---|
+, <<
|
Concatenate Operator |
<<= +=
|
Append Operator |
- |
Remove Operator |
-= |
Remove In-place Operator |
* |
Repeat Operator |
*= |
Repeat In-place Operator |
++ --
|
Increment and Decrement Operators |
Regular Expression Operators
| Operator(s) | Type |
|---|---|
=~ |
Find |
==~ |
Match |
Collection Operators
| Operator(s) | Type |
|---|---|
in |
Membership Operator |
* |
Spread Operator |
*. |
Spread-Dot Operator |
.. |
Range Operator |
[] |
Subscript Operator |
Object Operators
| Operator(s) | Type |
|---|---|
?. |
Safe Navigation Operator |
.@ |
Field Operator |
.& |
Method Reference |
as |
Casting Operator |
is |
Identity Operator |
instanceof, in
|
Type Comparison |
24. Operator Overloading
Groovy supports something called “operator overloading” and it’s possible for classes to determine how they want operators to behave. Throughout this tutorial I’ll provide some examples of overloading but before we go too far, let’s take a look at what “operator overloading” actually means.
The C++ language provides a mechanism for programmers to customise how operators such as + (plus) and - (minus) work. This functionality isn’t provided in Java but is available to Groovy programmers. Essentially, a class can include certain methods that replace (overload) the default implementation - as such methods are tied to specific operators.
Consider the + operator, as seen in many great additions. You can use the operator in a statement such as 10 + 2 but you can also use the plus method instead: 10.plus(2). I’d argue (strongly) that using the plus method in your code will be far less readable. However, you should be able to see that using the + operator actually results in the plus method being called.
This means that you can use operator overloading for evil - say, by creating a numerical class that ignores all mathematical sense. Aside from developer practical jokes you’ll probably only use operator overloading every now and then. Where it does become extremely useful is in the core Groovy classes and the Groovy extensions to the JDK.
In the coming chapters you’ll see a range of operator usage that isn’t available to the Java developer but made available through Groovy’s extensions to the JDK and through the GAPI.
To highlight all of this, operator overloading can be used in classes via the methods associated in the following table:
| Method | Operator |
|---|---|
| plus | + |
| minus | - |
| div | / |
| mod | % |
| multiply | * |
| power | ** |
| equals | == |
| compareTo |
<=>, >, <
|
| rightShift | >> |
| leftShift | << |
| next | ++ |
| previous | -- |
Throughout the tutorials on operators I’ll provide information as to how certain functionality is obtained through operator overloading. Feel free to glaze past these sections - they’re mainly there to explain why/how stuff is happening.
25. Numeric Operators
The following chapters will describe each of the numerical operators in further detail. In this tutorial I just lay the list of operators out for you to quickly see and then describe operator precedence - the order in which the operators are evaluated.
| Operator(s) | Type |
|---|---|
= |
Simple Assignment Operator |
! |
Logical Complement Operator |
== !=
|
Equality Operators |
+ - * / %, **
|
Arithmetic Operators |
> < <= >=
|
Relational Operators |
++ --
|
Increment and Decrement Operators |
&& || ?:
|
Conditional Operators |
<< >> >>> ~ & | ^
|
Bitwise Operators |
+= -= *= /= &= |= ^= %= <<= >>= >>>=
|
The Compound Assignment Operators |
Operator Precedence
Operator precedence describes the order in which operators are evaluated. For example, most people know that the multiplication operator is evaluated before the addition, resulting in the following code displaying 20 (and not 60):
println 10 + 2 * 5
Parentheses can be used to denote the need for an operator to be evaluated first, allowing the following code to give us 60:
def result = (10 + 2) * 5
println result
Operators with the same (equal) precedence (e.g. + and -) are evaluated depending on their associativity. There are three types of associativity:
- Left-associative: where the operators are grouped left to right
- Associative: where operators are grouped arbitrarily
- Not seen in Groovy
- Right-associative: where the operators are grouped right to left
For example, the additive operators (+ and -) are left associative, meaning that they are evaluated left to right. The expression 6 + 2 - 4 is evaluated as the result of 6 + 2 minus 4.
The simple assignment operator (=) is right associative, resulting in the following code displaying a result of 2:
def a = 10
def b = 5
def c = 2
a = b = c
println a
Order of Precedence
The order of precedence (highest to lowest) for the arithmetic operators is as follows:
| Operator | Example | |
|---|---|---|
| Postfix increment and decrement |
n++, n--
|
|
| Unary operators | ||
| Positive and negative | -10 |
|
| Prefix increment and decrement |
++2, --1
|
|
| Logical complement | !true |
|
| Bitwise complement | ~0x64 |
|
| Power | 10**2 |
|
| Multiplicative |
10 * 2, 6 / 3
|
|
| Additive |
5 + 5, 10 - 2
|
|
| Shift | >> |
|
| Relational | 10 > 4 |
|
| Equality | 1 == 1 |
|
| Bitwise AND | & |
|
| Bitwise XOR | ^ |
|
| Bitwise OR | | |
|
| Logical AND | true && false |
|
| Logical OR | true || false |
|
| Ternary | 10 > 2? true: false |
|
| Assignment (simple and compound) |
10 += 2, var = 9
|
26. Simple Assignment Operator
| Operator |
|---|
= |
The equals (=) sign is used to assign a value to a variable:
def age = 101
def name = "Fred"
In the following code the variable count is assigned the numeric value 10:
def count = 10
If we then wanted to compare count with another value (11) we need to use the == operator:
if (count == 11) println "Count is 11"
Use of = in the comparison will cause a compilation error:
if (count = 11) println "Count is 11"
Rest assured that if you accidentally use the simple assignment operator (=) instead of the equality operator (==) you’ll not be the first in making that mistake.
27. The Complement Operator
The exclamation (!) sign is used switch a value to its opposite boolean value. In boolean algebra1 this is referred to as a Not (or negation) operator.
| Operator |
|---|
! |
The following example makes sure that “not true” is the same as “false”:
assert !true == false
The complement operator results in the following:
| Value | Complement |
|---|---|
true |
false |
false |
true |
28. Equality Operators
The equality operators return a boolean (true or false) result from a comparison.
| Operator | Name |
|---|---|
| == | Equal to |
| != | Not equal to |
All of the following comparisons evaluate as being true:
assert -99 == -99
assert 'koala' == 'koala'
assert 'cat' != 'dog'
assert 6 != 7
def domesticAnimal = 'dog'
def wildAnimal = 'lion'
assert domesticAnimal != wildAnimal
def str1 = 'Hello'
def str2 = 'Hello'
assert str1 == str2
What Is Equality?
Equality can be a little tricky - both for Groovy and humanity. Think about the statement “Hey cool, we have the same car!”. This could mean that we have the same make and model but different instances of a car or it could mean that we share a car.
is and ==def obj1 = new Object()
def obj2 = new Object()
def obj3 = obj1
assert obj1.is(obj3)
assert ! obj1.is(obj2)
assert obj1 != obj2
assert obj1 == obj3
assert ! obj1.equals(obj2)
assert obj1.equals(obj3)
Precedence
In the following example, the equality operator (!=) is evaluated before the assignment operator (=), resulting in the value of truth being the boolean value true:
def truth = 'cats' != 'dogs'
assert truth == true
Overloading Equality
It is possible to define a custom implementation of == by overriding the equals(Object obj) method. This can be handy if your object has a simple method for determining equality, such as comparing staff members by their ID:
==class StaffMember {
def id
@Override
boolean equals(obj) {
if (this.id == obj.id) {
return true
} else {
return false
}
}
}
def fred = new StaffMember(id: 12)
def jan = new StaffMember(id: 47)
def janet = new StaffMember(id: 47)
assert fred != jan
assert jan == janet
The Groovy package groovy.transform provides a handy annotation that generates an equals implementation which compares the object’s properties. This reduces the previous StaffMember class to even fewer lines of code:
EqualsAndHashCode@groovy.transform.EqualsAndHashCode
class StaffMember {
def id
}
def fred = new StaffMember(id: 12)
def jan = new StaffMember(id: 47)
def janet = new StaffMember(id: 47)
assert fred != jan
assert jan == janet
29. Arithmetic operators
The five arithmetic operators (+, -, *, /, %) are familiar to most people from their early school days.
Additive Operators
| Operator | Name |
|---|---|
| + | Plus |
| - | Minus |
The additive operators provide for basic addition and subtraction.
assert 1 + 1 == 2
assert 10 - 9 == 1
Additive operators are left-associative - they are assessed from left to right:
assert 1 + 4 - 3 == 2
Multiplicative Operators
| Operator | Name |
|---|---|
| * | Multiply |
| / | Divide |
| % | Remainder |
The remainder operator (%) is also commonly referred to as the modulus operator and returns the remainder of a division:
assert 13 % 2 == 1
Multiplicative operators are left-associative:
assert 10 * 6 / 2 == 30
The Power operator
| Operator | Name |
|---|---|
| ** | Power |
The power operator (**) is used to raise a number to the power of the second number:
assert 5**3 == 125
Precedence
Multiplicative operators have precedence over additive operators.
assert 10 - 1 * 10 == 0
If the result above is surprising and the expected result was 90 then parentheses “()” should have been used:
assert (10 - 1) * 10 == 90
The elements within parentheses have precedence over the rest of the evaluation. This results in (10 - 1) being evaluated first and the result being multiplied by 10.
If we consider Pythagoras’ theorem: (
) the operator precedence will yield the correct answer without requiring parentheses:
assert 3 * 3 + 4 * 4 == 5 * 5
However, we could use parentheses purely for the sake of clarity:
assert (3 * 3) + (4 * 4) == (5 * 5)
Nested parentheses can be used to further delineate an expression. The innermost parentheses are evaluated first, then moving outwards:
assert ((10 - 1) * 10) / 2 == 45
In the equation above, (10 - 1) is evaluated first, the result (9) is then multiplied by 10 and that result (90) being divided by 2.
For significantly more complex calculations such as the quadratic equation (below) parentheses are required if the calculation is to be performed in a single expression:

def a = 5
def b = 6
def c = 1
def x
x = ((-1 * b) + Math.sqrt((b**2) - (4 * a * c))) / (2 * a)
assert x == -0.2
30. Relational Operators
Similar to the Equality Operators, the expressions involving Relational Operators return a boolean result (true or false).
| Operator | Name |
|---|---|
| > | Greater than |
| >= | Greater than or equal to |
| < | Less than |
| <= | Less than or equal to |
<=> |
Spaceship |
All of the following operations resolve to true:
trueassert 5 > 2
assert 4 >= 3
assert 4 >= 4
assert 8 < 9
assert 6 <= 7
assert 7 <= 7
Ordinarily, the operands used in a relational comparison can be compared in a meaningful manner. If they are different data types then the operands need to be able to find a common type for comparison (such as both being numbers) - the following code will cause and exception because Groovy can’t be expected compare a string with a number in this way:
if ('easy' < 123) println "It's easier than 123"
Spaceship
The spaceship operator comes from the Perl programming language. The Spaceship operator is most often seen where sorting is done.
| Operator |
|---|
<=> |
In the example below the sort function uses the closure to define the sort algorithm and this is where the spaceship lands:
def nums = [42, -99, 6.3, 1, 612, 1, -128, 28, 0]
//Descending
println nums.sort{n1, n2 -> n2<=>n1 }
//Ascending
println nums.sort{n1, n2 -> n1<=>n2 }
The following table indicates the result for spaceship expressions (LHS = left-hand side, RHS = right-hand side):
| Expression | Result |
|---|---|
| LHS less than RHS | -1 |
| LHS equals RHS | 0 |
| LHS greater than RHS | 1 |
The following assertions all resolve as true:
assert 2 <=> 2 == 0
assert 1 <=> 2 == -1
assert 2 <=> 1 == 1
Overloading the relational operators
The compareTo method is used by Groovy to assess the result of relational operations:
assert 1.compareTo(2) == -1
Java’s Comparable interface is implemented by classes that allow instances to be compared. Custom classes can determine their own appropriate algorithm for the Comparable’s compareTo method and this will be available when you use the relational operators.
class Num implements Comparable {
def val
@Override
int compareTo(obj) {
if (val < obj.val) {
return -1
} else if (val > obj.val) {
return 1
} else {
return 0
}
}
}
def a = new Num(val: 2)
def b = new Num(val: 5)
def c = new Num(val: 2)
assert a < b
assert b > a
assert a != b
assert a == c
You’ll notice that I’ve tested a != b and a == c - these equality operators actually call the compareTo method. There’s been a bit of discussion about how Groovy handles == and the underlying equals and compareTo methods so if you’re looking to overload these operators it’d be worth your time checking up on what the Groovy developers are planning.
31. Increment and Decrement Operators
The increment operator increments a value to its next value. When you increment or decrement a variable using ++ or -- the variable is modified to the new value.
| Operator | Name |
|---|---|
| ++ | Increment |
| – | Decrement |
The increment and decrement operators come in two flavours, prefix and postfix:
- Prefixes are assessed before the statement is evaluated
assert ++5 == 6
- Postfixes are assessed after the statement is evaluated
assert 5++ == 5
assert 10++ == 10
assert ++10 == 11
assert --10 == 9
assert 10-- == 10
The increment and decrement behaves differently depending on the type of value being used.
Booleans don’t increment/decrement
Numbers increment/decrement by 1:
def num = 10
num++
assert num == 11
Characters move to the previous (--) or next (++) character:
def ch = 'c'
ch--
assert ch == 'b'
Strings are a little odd and it is the last character in the string that is affected:
def str = 'hello'
str++
assert str == 'hellp'
Enums1 will cycle through the enum values:
enum Priority {
LOW, MEDIUM, HIGH
}
def task = Priority.LOW
task++
assert task == Priority.MEDIUM
BUT be aware that you’ll cycle back to the beginning of the value list. The following example is a good example of where you can easily get caught out:
def task = Priority.LOW
task--
assert task == Priority.HIGH
Overloading the Increment and Decrement Operators
By overloading next and previous methods, a custom class can support the increment and decrement operators.
The example below demonstrates a class that increments/decrements by 2:
class StepTwo extends Object {
def value
StepTwo(val) {
this.value = val
}
def next() {
value += 2
return this
}
def previous() {
value -= 2
return this
}
String toString() {
return "I have a value of ${this.value}"
}
}
def two = new StepTwo(3)
println two
two++
println two
two--
println two
- We’ll get to Enums much later.↩
32. Conditional Operators
You’ll most often see Conditional-and (&&) and Conditional-or (||) used in conditional statements such as if. We also use them in expressions such as assert to determine if a statement is true or false.
The Conditional Operator (?:) is a short-form variant of the if-else statement that really helps with code readability. It’s also referred to as the “elvis” operator and the “ternary” operator.
| Operator | Name |
|---|---|
&& |
Conditional-and |
|| |
Conditional-or |
?: |
Conditional operator |
What Is Truth?
All of the following statements resolve as true and the assertions all pass:
assert 1
assert true
assert 'Hello'
Obviously false is false but so is 0, '' (empty string) and null.
The complement operator (!) can be used to negate an expression, allowing the following assertions to pass:
assert !false
assert !0
assert !''
assert !null
Evaluation
Conditional operators are evaluated left-to-right. The assertion in the following code passes as the result of true && false is false but is then or’d with true, resulting in true:
assert true && false || true
Conditional-And
Conditional-and uses the boolean AND to determine if a statement is true or false. In order to be true, both the left-hand and right-hand operands must evaluate to true, as described in the truth table below:
| LHS | RHS | Result |
|---|---|---|
| 0 | 0 | 0 |
| 1 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 1 | 1 |
In a conditional-and statement, both expressions are always evaluated. In the example below, ++counter is evaluated (giving counter now equal to 1) before the conditional is assessed:
def counter = 0
def result = true && ++counter
assert result == true
assert counter == 1
Conditional-Or
Conditional-or uses the boolean OR to determine if a statement is true or false. In order to be true, either the left-hand or right-hand operands must evaluate to true, as described in the truth table below:
| LHS | RHS | Result |
|---|---|---|
| 0 | 0 | 0 |
| 1 | 0 | 1 |
| 0 | 1 | 1 |
| 1 | 1 | 1 |
In a conditional-or statement, each expression is evaluated in left-to-right order until either an expression resolves to true or no expressions are left (resulting in false overall). Once an expression is evaluated to true no further evaluation of the conditional is performed. This is important to keep in mind if you have an expression performing an operation that you later rely on as it may never be evaluated. In the example below I demonstrate a similar block of code used in the conditional-and section but I’ll use a conditional-or. The final assertion (assert counter == 1) will fail as ++counter is never evaluated:
def counter = 0
def result = true || ++counter
assert result == true
assert counter == 1
Conditional Operator
The conditional operator (?:) is most commonly used when assigning a value to a variable. A conditional expression is used for the first operand and placed to the left of the question-mark. If this resolves to true then the second operand is evaluated, otherwise the third operand is evaluated. This sounds a little confusing so let’s look at an example:
def salary = 100000
def taxBracket = salary < 75000 ? 'Bracket 1': 'Bracket 2'
assert taxBracket == 'Bracket 2'
In the code above the relational expression (salary < 75000) is evaluated and, in this case, resolves to false and the third operand (Bracket 2) is evaluated and assigned to taxBracket. As the operand is just a string there’s no real evaluation but we can use any expression that will return a result.
The code below will calculate income tax based on the person’s income:
def salary = 100000
def tax = salary < 75000 ? salary * 0.1: salary * 0.2
assert tax == 20000
A major benefit of the conditional operator is readability. Consider the previous code being re-written using an if statement and I trust you’ll see that ?: makes for more compact and readable code:
def salary = 100000
def tax = 0
if (salary < 75000) {
tax = salary * 0.1
} else {
tax = salary * 0.2
}
assert tax == 20000
Default values
The conditional operator is also really useful for default values - these are used when you want to make sure a variable always has a value. In the code below the Person class has been prepared to ensure that any instance that has not been explicitly given a value for the name property is assigned Anonymous as the name:
?: to set a default valueclass Person {
def name
def Person(name = '') {
setName(name)
}
def setName(name) {
this.name = name ?: 'Anonymous'
}
}
def jim = new Person()
println jim.name
Instead of writing this.name = name ? name: 'Anonymous' you’ll notice that I didn’t provide a second operand. This is another bonus for the ternary operator - if the conditional resolves to true and no second operand is provided, the result of the conditional is returned. This is a boring way to say that in this.name = name ?: 'Anonymous' if name is not false then it is assigned to this.name.
Avoiding NPEs
NPEs (Null-Pointer Expressions) are the bane of Java and Groovy programmers. You’ll see a lot of code checking for null and this reduces readability. This is usually in the form if (myObj != null) {...} or using the conditional operator (myObj != null) ? ... : ....
As null evaluates to false in Groovy, the conditional operator provides a compact means by which to check if an object is null before trying to access the object:
def myObj = null
def dump = myObj ? myObj.dump() : ''
In the example above I test myObj and, if it isn’t null then dump is given the value returned by myObj.dump(). Otherwise an empty string ('') is returned.
As an alternative, the safe-navigation operator (?.) can be used to test for null prior to attempting access on the object:
def myObj = null
def dump = myObj?.dump()
The safe navigation operator is discussed further under Object Operators.
In Java
In the code below I’ve provided a small example of checking for null in Java. You’ll note that we need to be explicit on our conditional ((t == null)):
public class NullTest {
public static void main(String[] args) {
NullTest t = null;
String output = (t == null) ? "Null" : "Not null";
System.out.println(output);
}
}
To prove that I’m not just bloating my code to prove a point, here’s a slightly more compact version of the example:
public class NullTest {
public static void main(String[] args) {
NullTest t = null;
System.out.println(t==null ? "Null" : "Not null");
}
}
You’ll note that I didn’t bother with the output variable and dropped the parentheses (they’re not required). The code is reasonably compact but rewritten in Groovy it gets even tidier:
class NullTest {
static main(args) {
def t = null
println(t ? 'Not null' : 'Null')
}
}
You still need to check for null in Groovy but the shortened conditional operator and the safe navigation operator really do help cut down on the boiler-plate stuff.
33. Bitwise Operators
I have to admit that I haven’t seen many instances of bitwise manipulation since my university assignments. That’s not to say they’re not used or not important - I’ve just not done a lot of programming that’s called on bitwise operators.
| Operator | Name |
|---|---|
| & | Bitwise AND |
| | | Bitwise OR |
| ^ | Bitwise XOR1 |
| ~ | Bitwise negation (Not) |
| >> | Right shift |
| >>> | Right shift unsigned |
| << | Left shift |
Truth Tables
Truth tables describe the results of various logical operations. The truth tables below illustrate the NOT, AND, OR and XOR logic.
Not (~) |
Result |
|---|---|
| Not 0 | 0 |
| Not 1 | 1 |
And (&) |
Result |
|---|---|
| 0 AND 0 | 0 |
| 0 AND 1 | 0 |
| 1 AND 0 | 0 |
| 1 AND 1 | 1 |
Or (|) |
Result |
|---|---|
| 0 OR 0 | 0 |
| 0 OR 1 | 1 |
| 1 OR 0 | 1 |
| 1 OR 1 | 1 |
Xor (^) |
Result |
|---|---|
| 0 XOR 0 | 0 |
| 0 XOR 1 | 1 |
| 1 XOR 0 | 1 |
| 1 XOR 1 | 0 |
Flag example
The unix file permission scheme uses binary flags for read, write and execute permissions on files. You see them when you run ls -l as something like -rwxr-xr-x. Essentially, this is a set of flags where binary 1 turns on that permission and binary 0 turns it off. The three elements I’ll look at here are read (r), write (w) and execute (x):
- READ has the binary value 001 (decimal 1)
- WRITE has the binary value 010 (decimal 2)
- EXECUTE has the binary value 100 (decimal 4)
Let’s look at the example code first and then I’ll discuss it:
//Create global variables for the permissions
READ = 0b100
WRITE = 0b010
EXECUTE = 0b001
println 'Checking for READ:'
checkFile(READ)
println 'Checking for WRITE:'
checkFile(WRITE)
println 'Checking for READ or EXECUTE:'
checkFile(READ | EXECUTE)
def checkFile (check) {
def fileList = [:]
for (i in 0..7) {
fileList["File $i"] = i
}
for (file in fileList) {
if (file.value & check) {
println "$file.key (${displayFilePermission(file.value)}) meets crit\
eria"
}
}
}
def displayFilePermission(val) {
def retval = ""
retval <<= (READ & val)? 'r': '-'
retval <<= (WRITE & val)? 'w': '-'
retval <<= (EXECUTE & val)? 'x': '-'
return retval
}
First up I set the flags for each of the three elements using the 0b prefix to indicate binary numbers:
READ = 0b100
WRITE = 0b010
EXECUTE = 0b001
I then call my checkFile method to see which permissions match what I’m seeking. The third call to checkFile is the more interesting as I OR two flags: READ | EXECUTE. If I OR the READ flag (100) with the EXECUTE flag (001) I get 101 (decimal 5):
| Value | Binary | Operator | ||
|---|---|---|---|---|
| READ | 1 | 0 | 0 | OR |
| EXECUTE | 0 | 0 | 1 | |
| Result | 1 | 0 | 1 | = |
The checkFile method does the checking for me. The first part of the method just creates a set of possible files - enough to cover the various variations of the rwx elements:
def fileList = [:]
for (i in 0..7) {
fileList["File $i"] = i
}
It’s the second half of checkFile that does the important stuff:
for (file in fileList) {
if (file.value & check) {
println "$file.key (${displayFilePermission(file.value)}) meets criteria"
}
}
The if (file.value & check) performs an AND on the check requested (e.g. READ) and the file’s permissions. If the AND returns a result greater than 0 then the file’s permission match the check. For example, a file with execute permission (--x) meets the READ | EXECUTE criteria:
| Item | Value | Binary | Operator | ||
|---|---|---|---|---|---|
check |
READ | EXECUTE | 1 | 0 | 1 | AND |
file |
--x |
0 | 0 | 1 | |
| Result | 0 | 0 | 1 | = |
A file with read and write permission (rw-) also matches:
| Item | Value | Binary | Operator | ||
|---|---|---|---|---|---|
check |
READ | EXECUTE | 1 | 0 | 1 | AND |
file |
rw- |
1 | 1 | 0 | |
| Result | 1 | 0 | 0 | = |
However, a file with only the write permission (-w-) will not successfully match:
| Item | Value | Binary | Operator | ||
|---|---|---|---|---|---|
check |
READ | EXECUTE | 1 | 0 | 1 | AND |
file |
-w- |
0 | 1 | 0 | |
| Result | 0 | 0 | 0 | = |
Lastly, the displayFilePermission method just helps me display the permissions in the rwx format.
Some other quick points follow:
I can negate (~) a value to indicate that I want the inverse of a value, rather than ORing the other options individually:
println 'Checking for WRITE or EXECUTE:'
checkFile(~READ)
I can XOR (^) to aggregate the permissions but ignore intersections (where both variables contain the same flag):
def file1 = READ | EXECUTE
def file2 = WRITE | EXECUTE
println 'File 1: ' << displayFilePermission(file1)
println 'File 2: ' << displayFilePermission(file2)
println 'Result: ' << displayFilePermission(file1 ^ file2)
Shift Example
Shifting just moves the bits in a binary to the left (<<) or to the right (>>), depending on the left-hand operand. If we take the following list as a starting point we can see how progressive shifts left change a value:
- 0001 (binary) = 1 (decimal)
- 0010 (binary) = 2 (decimal)
- 0100 (binary) = 4 (decimal)
- 1000 (binary) = 8 (decimal)
With this is mind the following code demonstrates the left- and right-shift operators:
assert 2 << 1 == 4 //Left-shift once
assert 2 << 2 == 8 //Left-shift twice
assert 2 >> 1 == 1 //Right-shift once
The code below displays a table in which each row represents a value that’s be left-shifted by one position more than the prior row:
def value = 0b0000_0000_0000_0000_1111_1111
println '| Shift | Hex | Decimal | Octal | Binary |'
println '|---------|----------|----------|----------|--------------------------|'
(0..16).each {
def shifted = value << it
def hexDisplay = '0x' << Integer.toHexString(shifted).padLeft(6, '0')
def binDisplay = Integer.toBinaryString(shifted).padLeft(24, '0')
def decDisplay = "$shifted".padLeft(8, ' ')
def octDisplay = Integer.toOctalString(shifted).padLeft(8, ' ')
def shiftDisplay = "$it".padLeft(7, ' ')
println "| $shiftDisplay | $hexDisplay | $decDisplay | $octDisplay | $binDis\
play |"
}
println '|---------|----------|----------|----------|--------------------------|'
- Known as an Exclusive OR↩
34. Compound Assignment Operators
The compound assignment operators1 really just conflate an operation that involves a variable which is, it turn, used to store the result. Let’s look at an example to make this clearer. In the example below I really just want to add 10 to the cost variable:
def cost = 20
cost = cost + 10
assert cost == 30
By using a compound assignment operator I can clean up the code (in a very minor way) by performing the operation ‘in place’:
def cost = 20
cost += 10
assert cost == 30
| Operator | Name |
|---|---|
| *= | Multiply |
| /= | Divide |
| %= | Remainder |
| += | Plus |
| -= | Minus |
| **= | Power |
| <<= | Bitwise left-shift |
| >>= | Bitwise right-shift |
| >>>= | Bitwise unsigned right-shift |
| &= | Bitwise And |
| ^= | Bitwise Xor |
| |= | Bitwise Or |
- Also known as augmented assignment operators. See http://en.wikipedia.org/wiki/Augmented_assignment↩
35. String Operators
You’ll spend a lot of your career manipulating strings so anything that makes them less of a hassle is nice. Groovy helps you with the following operators overloaded for your string work.
| Operator(s) | Type |
|---|---|
+, <<
|
Concatenate Operator |
<<= +=
|
Append Operator |
- |
Remove Operator |
-= |
Remove In-place Operator |
* |
Repeat Operator |
*= |
Repeat In-place Operator |
++ --
|
Increment and Decrement Operators |
Concatenate Operator
The concatenate operator joins two strings together:
println 'It was the best of times.' << 'It was the worst of times.'
The above example is rather daft as we could have just put both strings together in the same set of quotes. You’re more likely to see strings added to over the course of a program:
def quote = 'It was the best of times.'
quote = quote << 'It was the worst of times.'
println quote
Instead of using the concatenate you could have just used string interpolation:
def quote = 'It was the best of times.'
quote = "$quote It was the worst of times."
println quote
The + operator is used in the same manner as <<:
def quote = 'It was the best of times.'
quote = quote + 'It was the worst of times.'
println quote
As you’ll see in later in this chapter it’s best to use << over +
Concatenation and Types
When you concatenate a string with a number Groovy will cast the number into a string. That means you can end up with 1 + 1 = 11 as the code below demonstrates:
assert '1' + 1 == '11'
assert 1 + '1' == '11'
If you’re really wanting to add a string to a number you need to make sure you explicitly turn the string into a number:
assert '1'.toInteger() + 1 == 2
This may all sound a bit odd now but if you’re trying to work out why your program’s maths seems all wrong it’s worth looking into where strings and numbers are being mashed together.
Append Operator
The append operator (<<=) conflates the assignment (=) and concatenate operators:
def quote = 'It was the best of times.'
quote <<= 'It was the worst of times.'
println quote
This saves you from having to use quote = quote << 'It was the worst of times.'
Remove Operator
The remove operator (-) removes the first instance of a string or regular expression from another string. The easiest form just removes the first instance of a specific string - in the case of the example below, ‘of ‘ is removed:
quote = 'It was the worst of times.' - 'of '
println quote
The example above will display It was the worst times.
A regular expression pattern can also be used if you want to use a pattern. In the example below, the first occurrence of “bat” or “rat” is removed, resulting in cat rat monkey:
println 'cat bat rat monkey' - ~/[br]at/
Remove In-Place Operator
Works just like the remove operator (-) but does the match to the variable as well as modifying it. As for the first remove example, a string can be provided for removal:
quote = 'It was the worst of times.'
quote -= 'of '
println quote
…and it can also use patterns:
def str = 'cat bat rat monkey'
str -= ~/[br]at/
println str
Repeat Operator
This is a great operator for those that love repetition! Let’s print out hello ten-times, each time one a new line:
print 'hello\n' * 10
Repeat In-PlaceOperator
This one applies the multiplier against the variable and stores the result back in the variable:
def complaint = 'ow'
complaint *= 10
println complaint
I’ll leave it to you to see what happens :)
Increment and Decrement Operators
The increment operator will move the last character of the string to its next value:
def str = 'hello'
str++
assert str == 'hellp'
The increment/decrement actually works across the Unicode character codes1 so don’t expect code to just use ‘a’ to ‘z’:
def str = 'fo%'
str--
assert str == 'fo$'
For a small experiment, try the following code - it will display a subset of the Unicode characters:
//\u00A1 is the Unicode character for an inverted exclamation mark
def chr = '\u00A1'
for (i in 161..191) {
println chr
chr++
}
I’m sure that this is useful somewhere…..
Warning: Strings Are Expensive!
Many programs build strings up over the course of their operation. This can start becoming very expensive in terms of program resources such as memory because, without the correct approach, the JVM has to copy strings to new memory locations each time you use concatenation.
Java developers turn to the StringBuilder and StringBuffer classes to make their string building more efficient. Groovy developers using dynamic types can use a few tricks to stay dynamic and ensure efficiency.
Let’s take a look at two approaches to building a string. In the first example I’ll use the += operator and perform 1000 concatenations:
+= concatenationimport java.text.DecimalFormat
def quote = 'It was the best of times. It was the worst of times.\n'
def str = ""
def startTime = System.nanoTime()
1000.times {
str += quote
}
def endTime = System.nanoTime()
DecimalFormat formatter = new DecimalFormat("#,###")
def duration = formatter.format(endTime - startTime)
println "That took $duration nano seconds"
In the next example I’ll change just 1 thing: I’ll use the <<= operator rather than +=:
<<= concatenationimport java.text.DecimalFormat
def quote = 'It was the best of times. It was the worst of times.\n'
def str = ""
def startTime = System.nanoTime()
1000.times {
str <<= quote
}
def endTime = System.nanoTime()
DecimalFormat formatter = new DecimalFormat("#,###")
def duration = formatter.format(endTime - startTime)
println "That took $duration nano seconds"
When I run these scripts in groovyConsole I can see that the results are very different. When I ran each test 100 times and averaged the result I got:
- Example 1 (using
+=): 24,215,520 ns - Example 2 (using
<<=): 191,490 ns
To me this is evidence enough for me to use ‘<<=’ over +=!
Templates
If you find yourself building strings around boilerplate text - such as a form letter - consider using Groovy’s templating system.
- See the Unicode Character Code Charts↩
36. Regular Expression Operators
The earlier tutorial on Regular Expressions covered regular expression (pattern) variables and described the find and match methods. These operators are similar to these methods but return true or false if the pattern is found in (find) or matches (match) the first operand (a string).
| Operator(s) | Type |
|---|---|
=~ |
Find |
==~ |
Match |
For these operations, the left-hand operand must be a string and the right-hand operand a regular expression pattern.
Find (=~)
Returns true if the string on the left-side contains the pattern on the right of the operator.
def regex = ~/:[0-9]+/
def httpUrl1 = 'http://www.example.com:8080/'
def httpUrl2 = 'http://www.example.com/'
assert httpUrl1 =~ regex
assert ! (httpUrl2 =~ regex)
Match (==~)
Returns true if the string on the left-side matches (completely) the pattern provided on the right of the operator
def regex = ~/https?:\/\/.*/
def httpUrl = 'http://www.example.com/'
def ftpUrl = 'ftp://ftp.example.com/'
assert httpUrl ==~ regex
assert ! (ftpUrl ==~ regex)
37. Collection operators
A number of operators are provided for working with Lists and Maps. Some overload operators such as + and << whilst others (such as in) are more collection-oriented. Certain operators work with both Lists and Maps whilst others apply to only one.
| Operator(s) | Type |
|---|---|
in |
Membership Operator |
<< |
Append operator |
+ |
Addition operator |
- |
Subtraction operator |
+= -=
|
Compound assignment operators |
* |
Spread Operator |
*. |
Spread-Dot Operator |
.. |
Range Operator |
[] |
Subscript Operator |
This chapter won’t discuss the following operators as they’ve been described earlier:
- The Range operator creates a list of sequential values and is usually seen with numbers. This is how we created Range variables.
- The Subscript operator is used to access items in a List or a Map and this was discussed in the tutorial on collection variables.
To finish this chapter off I’ll do a little bit of mucking around with set theory.
Membership Operator (Lists and Maps)
The in operator is used to determine if an item is “in” a list or is a key in a map.
in operatorassert 6 in [1, 2, 6, 9]
assert !(3 in [1, 2, 6, 9])
def grades = ['Maths': 'A',
'English': 'C',
'Science': 'B'].asImmutable()
assert 'Science' in grades
assert !('French' in grades)
Append (Lists and Maps)
The << operator adds a new element to an existing list:
def friends = ['Frank', 'Larry']
friends << 'Jane'
println friends
It’s important to note that appending a list to a list will add a new element that contains the list in the right-hand operand:
def friends = ['Frank', 'Larry']
friends << ['Jane', 'Greg']
assert friends == ['Frank', 'Larry', ['Jane', 'Greg']]
In order to add the individual items of one list to another I need to use the addAll() method:
def friends = ['Frank', 'Larry']
friends.addAll(['Jane', 'Greg'])
assert friends == ['Frank', 'Larry', 'Jane', 'Greg']
I can also use << to append a new key:value pair to a map:
def grades = [:]
grades << ['Maths': 'A']
grades << ['English': 'C']
grades << ['Science': 'B']
println grades
If I was to add another line grades << ['Science': 'F'] to the code above, the value for Science would be changed to F as the map’s keys are unique.
Addition (Lists and Maps)
The addition operator (+) returns a new list with the right-hand operand added:
def friends = ['Frank', 'Larry']
assert friends + 'Jane' == ['Frank', 'Larry', 'Jane']
When we add two lists together we get a union of the two lists returned:
def friends = ['Frank', 'Larry']
assert friends + ['Jane', 'Greg'] == ['Frank', 'Larry', 'Jane', 'Greg']
Adding to a Set returns a set with the union sans any duplicates:
def set = [2, 4, 6, 8] as Set
assert set + [8, 10] == [2, 4, 6, 8, 10] as Set
The addition operator will either add a key:value pair to a map or alter the value held against an existing key. In the example below I create a new map item with a result for my French class and then change an existing map item with a reduced English score:
def grades = ['Maths': 'A',
'English': 'C',
'Science': 'B']
assert grades + ['French': 'F'] == ['Maths': 'A', 'English': 'C', 'Science': 'B'\
, 'French': 'F']
assert grades + ['English': 'D'] == ['Maths': 'A', 'English': 'D', 'Science': 'B\
']
Subtraction (Lists and Maps)
The subtraction (-) operator will return a new list with an element removed if the list contains the element:
assert [2, 4, 6, 8] - 6 == [2, 4, 8]
A list can also be subtracted from a list, returning a new list containing items in the left-hand operand ([2, 4, 6, 8]) that are not in the right-hand operand ([2, 6, 12]):
assert [2, 4, 6, 8] - [2, 6, 12] == [4, 8]
In the example below my attempt to remove Gary doesn’t do anything as he’s not in the list (this doesn’t cause an exception) but I do succeed in un-friending Frank:
def friends = ['Frank', 'Larry', 'Jane']
assert friends - 'Gary' == ['Frank', 'Larry', 'Jane']
assert friends - 'Frank' == ['Larry', 'Jane']
When subtraction is applied to a Map the right-hand operand needs to be a key:value pair. In the example below I attempt 3 things:
- I attempt to remove
['English': 'D']but it’s not ingradesso nothing happens - I attempt to remove
['French': 'F']but it’s not ingradesso nothing happens - I attempt to remove
['English': 'C']and it is ingradesso the removal occurs.
def grades = ['Maths': 'A',
'English': 'C',
'Science': 'B']
assert grades - ['English': 'D'] == ['Maths': 'A', 'English': 'C', 'Science': 'B\
']
assert grades - ['French': 'F'] == ['Maths': 'A', 'English': 'C', 'Science': 'B']
assert grades - ['English': 'C'] == ['Maths': 'A', 'Science': 'B']
Compound Assignment Operators (Lists and Maps)
Just as we saw with numbers, the addition and subtraction operators returns a value but don’t actually change the variable involved in the operation. To change the value of grades I would have needed to assign the resultant back into the variable as follows:
grades = grades + ['French': 'F']
assert grades == ['Maths': 'A', 'English': 'C', 'Science': 'B', 'French': 'F']
If we want to use the grades variable as the left-hand operand and change its value we can use the compound assignment operators. This means I could also have written the previous example using the += compound assignment:
grades += ['French': 'F']
assert grades == ['Maths': 'A', 'English': 'C', 'Science': 'B', 'French': 'F']
Using the append operator in its compound form (<<=) is redundant.
Immutability and Assignment
Consider the following code and see if you’re surprised:
def grades = ['Maths': 'A',
'English': 'C',
'Science': 'B'].asImmutable()
grades += ['French': 'F']
assert grades == ['Maths': 'A', 'English': 'C', 'Science': 'B', 'French': 'F']
Groovy let me change something that’s immutable! I should go to the mailing list and report this! The outrage!
Hang on! What asImmutable() does is set the elements of the list to be unchangeable but it doesn’t make the grades variable immutable. As the + operator actually returns a new list value, Groovy is correct in assigning that new value to grades.
If I’d used grades << ['French': 'F'] instead of grades += [‘French’: ‘F’] I would get a java.lang.UnsupportedOperationException as I’m actually trying to add a new element to grades.
If I really want to make grades completely immutable (constant) then I’d need to use the final modifier and declare grades within a class. The code below demonstrates how I’d set up the class and ensure that attempts to change grades cause an exception:
class Report {
final grades = ['Maths': 'A',
'English': 'C',
'Science': 'B'].asImmutable()
}
def myReport = new Report()
myReport.grades += ['French': 'F']
Running the code above will earn you a groovy.lang.ReadOnlyPropertyException.
Spread Operator (Lists)
The Spread operator extracts each element in the List into another list or a method’s parameters. This is helpful when you need to include a list’s individual items in another list or when your list can be used as parameters in a method call.
Extracting Into Lists
In the first example, one lists’s items are extracted into another list:
def list = [1, 2, 6, 9]
def list2 = [*list, 12, 34]
assert list2 == [1, 2, 6, 9, 12, 34]
This usage looks rather like the addAll() method but you may need to be mindful as to the position in which the list is extracted. The example below uses addAll() but results in list2 being ordered differently than in the previous example:
def list = [1, 2, 6, 9]
def list2 = [12, 34]
list2.addAll(list)
assert list2 == [12, 34, 1, 2, 6, 9]
In this last example I demonstrate an easy approach to creating a union of the two lists:
def list = [1, 2, 6, 9]
def list2 = [12, 34]
assert [*list, *list2] == [1, 2, 6, 9, 12, 34]
Extracting as Parameters
In the next example I extract the items in the score list out, each aligning with the parameters in the method signature:
def mean(num1, num2, num3) {
(num1 + num2 + num3) / 3
}
def scores = [4, 8, 3]
assert mean(*scores) == 5
That last example is a little bit of a goldilocks moment - I have exactly the same number of items in the list as the method has parameters. I also have a pretty limited version of the mean method - it only works on 3 numbers. However, a method with a varargs parameter is a little less fairy tale:
def mean(...nums) {
def total = 0
for (item in nums) {
total += item
}
return total / nums.size()
}
def scores = [4, 8, 3]
assert mean(*scores) == 5
One last example of using the spread operator:
def buyGroceries(...items) {
for (item in items) {
println item
}
}
def shoppingList = ['apples', 'cat food', 'cream']
buyGroceries(*shoppingList)
Multiply Operator
Note that you can use * as a form of multiplication involving lists but this doesn’t return a list containing each element multiplied by the right-hand operand. Rather, the returned list just contains the original list elements repeated by the number of times set by the right-hand operand. In the example below I get 2, 4, 6 repeated 4 times:
def list = [2, 4, 6]
println list * 4
Spread-Dot Operator (Lists)
The *. operator calls an action (method) on each item in the list and returns a new list containing the results. In the example below I call the reverse() method on each list element:
println(['carrot', 'cabbage', 'cauliflower']*.reverse())
The spread operator mimics the collect() method - with the previous example being equivalent to the following:
['carrot', 'cabbage', 'cauliflower'].collect{it?.reverse()}
The spread operator makes the method call using the “Safe navigation Operator” (?.) to make sure that the list element isn’t null - refer to the Object Operators tutorial for more information. In the next example I include a null in the list and the returned list features the null:
println(['carrot', 'cabbage', null, 'cauliflower']*.reverse())
For maps I can’t use *. so need to use the collect() method.
A Little Set Theory
Writing this chapter got me thinking about set theory and how various aspects can be achieved in Groovy lists.
Membership
The in method gives us a membership check:
assert 4 in [2, 4, 6, 8]
Union
The addition operator provides us with the ability to performs unions:
assert [2, 4, 6, 8] + [1, 3, 5, 7] == [2, 4, 6, 8, 1, 3, 5, 7]
Complements
The subtraction operator (-) gives us set complement (difference):
assert [2, 4, 6, 8] - [6, 8, 10] == [2, 4]
Intersection
The disjoint() method will return true if two lists don’t contain any intersecting elements:
assert [2, 4, 6, 8].disjoint([1, 3, 5, 7]) == true
If disjoint() returns false then some elements intersect.
def list1 = [2, 4, 6, 8]
def list2 = [6, 8, 10]
assert ([*list1, *list2] as Set) - (list1 - list2) - (list2 - list1) == [6, 8] a\
s Set
Guava Sets Library
All this got me thinking further and looking into Google’s Guava libraries - here’s some code that uses Guava to scratch my set itch:
@Grab(group='com.google.guava', module='guava', version='18.0')
import static com.google.common.collect.Sets.*
def list1 = [2, 4, 6, 8] as Set
def list2 = [6, 8, 10] as Set
println "Intersection: " << intersection(list1, list2)
println "Union: " << union(list1, list2)
println "Difference (list1 - list2): " << difference(list1, list2)
println "Difference (list2 - list1): " << difference(list2, list1)
println "Cartesian product of list1 and list2"
for (set in cartesianProduct(list1, list2)) {
println " - $set"
}
println "Powersets of list1: "
for (set in powerSet(list1)) {
println " - $set"
}
38. Object Operators
It could be argued that all operators are object operators as nearly every variable or value in Groovy is an object. However, these operators are all about working with and checking on the object’s structure.
| Operator(s) | Type |
|---|---|
?. |
Safe Navigation Operator |
as |
Casting Operator |
is |
Identity Operator |
instanceof in
|
Type Comparisons |
.@ |
Field Operator |
.& |
Method Reference |
Safe Navigation Operator
The Safe Navigation operator (?.) checks to make sure that a variable isn’t null before calling the requested method. Consider the following code:
class Person{
def name
}
def fred = new Person(name: 'Fred')
//various statements
fred = null
//various statements
println fred.name
As fred somehow became null at some point in the code, that call to fred.name causes a nasty java.lang.NullPointerException (aka the NPE). This happens a lot as variables (in this case fred) can end up being null for a number of reasons, including:
- The variable never gets set in the first place - perhaps the initialisation failed but we didn’t catch it properly
- A method returns
nullinstead of an object instance - We get passed a parameter that has
nullvalue.
In order to stop the NPE you’ll normally see developers using an if statement to check that the variable isn’t null before trying to call a method:
class Person{
def name
}
def fred = new Person(name: 'Fred')
//various statements
fred = null
//various statements
if (fred) {
println fred.name
}
Groovy’s Safe Navigation operator saves some time and code. In the code below, Groovy checks that the fred variable isn’t null before trying to access the name property - giving us a compact piece of code: fred?.name.
class Person{
def name
}
def fred = new Person(name: 'Fred')
//various statements
fred = null
//various statements
println fred?.name
You’ll see that “null” is displayed - this is because fred is null. Groovy doesn’t even try to access the name property.
Casting Operator
The Casting operator (as) changes the data type of a value or variable to the specified class. This is sometimes called “casting”, “type conversion” or “coercing”. You’ll have seen this in action when we created a Set:
def nums = [1, 6, 3, 9, 3] as Set
The as tells Groovy that you want to convert the item to be of the specified data type (class) - in the example above I use Set. The code below demonstrates a few more conversions:
assert 3.14 as Integer == 3
assert 101 as String == '101'
assert true as String == 'true'
assert '987' as Integer == 987
You’ll note that the cast can be lossy - 3.14 as Integer caused the value to be truncated to 3. Not all values can be cast to all types and code such as 'hello, world' as Integer causes an exception.
Identity Operator
The Identity operator (is) determines if two variables are referencing the same object instance. This “operator” is really a method that you call by using obj1.is(obj2) to check if obj1 and obj2 reference the same instance.
As we saw in the chapters on Equality Operators and Relational Operators, Groovy uses the == operator to determine if two objects are equivalent based on their state. Using == for this purpose is really useful and improves code readability but it means that the traditional Java use of == to determine if two objects reference the same instance needs a replacement in Groovy. The is method is that replacement.
In the code below I describe a Person class and use a very helpful annotation (@groovy.transform.EqualsAndHashCode) so that Groovy sets up the approach to determining if two instances of Person are the same - such that == returns true. I’ve decided that all people will have a unique identifier and, provided two instances have the same identifier, they’re the same person. This means that all three variations (fred, freddie, frederick) of the person with the ID 345 are equal (==) to each other. However, by using is I can see that, whilst fred and freddie point to the same instance of Person, frederick points to a different instance.
@groovy.transform.EqualsAndHashCode(includes="id")
class Person{
def id
def name
}
def fred = new Person(id: 345, name: 'Fred')
def freddie = fred
def frederick = new Person(id: 345, name: 'Frederick')
//Check that they're all the same person
assert fred == freddie
assert fred == frederick
assert freddie == frederick
//Check which variable points to the same instance
assert fred.is(freddie)
assert ! fred.is(frederick)
Type Comparison
The Type Comparison operators (instanceof and in) is used to determine if a variable is an instance of the specified class.
In this next example I check to make sure that fred is a Person:
class Person{
def name
}
def fred = new Person(name: 'Fred')
assert fred instanceof Person
assert fred in Person
Checking the variable’s type can be useful in dynamically typed languages such as Groovy as it lets us check before we call a property or method that may not be there:
class Person{
def name
}
def fred = new Person(name: 'Fred')
if (fred instanceof Person) {
println fred?.name
}
In my Person example I’m not really using the full benefits of object-oriented programming that we can leverage in Groovy - primarily because we’re yet to get up to that. However, trust me when I say that class hierarchies and interfaces give us a handy way to build up a family tree of classes and that we can use instanceof or in to check if the object instance has a legacy that helps us achieve an outcome. For example, the Integer and Float classes are a subclass (child) of the Number class.
In the example below I set up an add method that adds two numbers (handy!). Before I try to add those two numbers I use in to make sure they’re actually Numbers. If they aren’t, I throw an exception at you.
def add(num1, num2) {
if (num1 in Number && num2 in Number) {
return num1 + num2
}
throw new IllegalArgumentException('Parameters must be Numbers')
}
assert add(1, 6) == 7
assert add(3.14, 9.2) == 12.34
add('Rabbit', 'Flower')
Field Operator and Method Reference
The Field operator (.@) provides direct access to an object’s property (field) rather than using a getter/setter. Use this with a lot of caution or, even better, don’t use it at all.
The Method Reference operator (.&) returns a reference to an object method. This can be handy when you’d like to use the method as a closure. This is a very useful feature so use it at will!
In the example below I describe the Person class. When I then create an instance called example you’ll notice that:
-
example.name = 'Fred'causessetName()to be called -
println example.namecausesgetName()to be called -
example.@name = 'Jane'andprintln example.@nameboth access thenameproperty directly. -
def intro = example.&introduceSelfsetsintroas a pointer (closure) to theintroduceSelfmethod.- Which is then called using
intro()
- Which is then called using
class Person {
def name
def setName(name) {
println 'You called setName()'
this.name = name
}
def getName() {
println 'You called getName()'
return this.name
}
def introduceSelf() {
println "Hi, my name is ${this.name}"
}
}
def example = new Person()
//example.name actually calls the getter or setter
example.name = 'Fred'
println example.name
//example.@name directly access the field
example.@name = 'Jane'
println example.@name
//intro holds the reference to the introduceSelf method
def intro = example.&introduceSelf
//This next line calls introduceSelf()
intro()