This chapter covers the operators of the Groovy programming language.
1. Arithmetic operators
Groovy supports the usual familiar arithmetic operators you find in mathematics and in other programming languages like Java. All the Java arithmetic operators are supported. Let’s go through them in the following examples.
1.1. Normal arithmetic operators
The following binary arithmetic operators are available in Groovy:
Operator | Purpose | Remarks |
---|---|---|
|
addition |
|
|
subtraction |
|
|
multiplication |
|
|
division |
Use |
|
remainder |
|
|
power |
See the section about the power operation for more information on the return type of the operation. |
Here are a few examples of usage of those operators:
assert 1 + 2 == 3
assert 4 - 3 == 1
assert 3 * 5 == 15
assert 3 / 2 == 1.5
assert 10 % 3 == 1
assert 2 ** 3 == 8
1.2. Unary operators
The +
and -
operators are also available as unary operators:
assert +3 == 3
assert -4 == 0 - 4
assert -(-1) == 1 (1)
1 | Note the usage of parentheses to surround an expression to apply the unary minus to that surrounded expression. |
In terms of unary arithmetics operators, the ++
(increment) and --
(decrement) operators are available,
both in prefix and postfix notation:
def a = 2
def b = a++ * 3 (1)
assert a == 3 && b == 6
def c = 3
def d = c-- * 2 (2)
assert c == 2 && d == 6
def e = 1
def f = ++e + 3 (3)
assert e == 2 && f == 5
def g = 4
def h = --g + 1 (4)
assert g == 3 && h == 4
1 | The postfix increment will increment a after the expression has been evaluated and assigned into b |
2 | The postfix decrement will decrement c after the expression has been evaluated and assigned into d |
3 | The prefix increment will increment e before the expression is evaluated and assigned into f |
4 | The prefix decrement will decrement g before the expression is evaluated and assigned into h |
For the unary not operator on Booleans, see Conditional operators.
1.3. Assignment arithmetic operators
The binary arithmetic operators we have seen above are also available in an assignment form:
-
+=
-
-=
-
*=
-
/=
-
%=
-
**=
Let’s see them in action:
def a = 4
a += 3
assert a == 7
def b = 5
b -= 3
assert b == 2
def c = 5
c *= 3
assert c == 15
def d = 10
d /= 2
assert d == 5
def e = 10
e %= 3
assert e == 1
def f = 3
f **= 2
assert f == 9
2. Relational operators
Relational operators allow comparisons between objects, to know if two objects are the same or different, or if one is greater than, less than, or equal to the other.
The following operators are available:
Operator | Purpose |
---|---|
|
equal |
|
different |
|
less than |
|
less than or equal |
|
greater than |
|
greater than or equal |
|
identical (Since Groovy 3.0.0) |
|
not identical (Since Groovy 3.0.0) |
Here are some examples of simple number comparisons using these operators:
assert 1 + 2 == 3
assert 3 != 4
assert -2 < 3
assert 2 <= 2
assert 3 <= 4
assert 5 > 1
assert 5 >= -2
Both ===
and !==
are supported which are the same as calling the is()
method,
and negating a call to the is()
method respectively.
import groovy.transform.EqualsAndHashCode
@EqualsAndHashCode
class Creature { String type }
def cat = new Creature(type: 'cat')
def copyCat = cat
def lion = new Creature(type: 'cat')
assert cat.equals(lion) // Java logical equality
assert cat == lion // Groovy shorthand operator
assert cat.is(copyCat) // Groovy identity
assert cat === copyCat // operator shorthand
assert cat !== lion // negated operator shorthand
3. Logical operators
Groovy offers three logical operators for boolean expressions:
-
&&
: logical "and" -
||
: logical "or" -
!
: logical "not"
Let’s illustrate them with the following examples:
assert !false (1)
assert true && true (2)
assert true || false (3)
1 | "not" false is true |
2 | true "and" true is true |
3 | true "or" false is true |
3.1. Precedence
The logical "not" has a higher priority than the logical "and".
assert (!false && false) == false (1)
1 | Here, the assertion is true (as the expression in parentheses is false), because "not" has a higher precedence than "and", so it only applies to the first "false" term; otherwise, it would have applied to the result of the "and", turned it into true, and the assertion would have failed |
The logical "and" has a higher priority than the logical "or".
assert true || true && false (1)
1 | Here, the assertion is true, because "and" has a higher precedence than "or", therefore the "or" is executed last and returns true, having one true argument; otherwise, the "and" would have executed last and returned false, having one false argument, and the assertion would have failed |
3.2. Short-circuiting
The logical ||
operator supports short-circuiting: if the left operand is true, it knows that the result will be true in any case, so it won’t evaluate the right operand.
The right operand will be evaluated only if the left operand is false.
Likewise for the logical &&
operator: if the left operand is false, it knows that the result will be false in any case, so it won’t evaluate the right operand.
The right operand will be evaluated only if the left operand is true.
boolean checkIfCalled() { (1)
called = true
}
called = false
true || checkIfCalled()
assert !called (2)
called = false
false || checkIfCalled()
assert called (3)
called = false
false && checkIfCalled()
assert !called (4)
called = false
true && checkIfCalled()
assert called (5)
1 | We create a function that sets the called flag to true whenever it’s called |
2 | In the first case, after resetting the called flag, we confirm that if the left operand to || is true, the function is not called, as || short-circuits the evaluation of the right operand |
3 | In the second case, the left operand is false and so the function is called, as indicated by the fact our flag is now true |
4 | Likewise for && , we confirm that the function is not called with a false left operand |
5 | But the function is called with a true left operand |
4. Bitwise and bit shift operators
4.1. Bitwise operators
Groovy offers four bitwise operators:
-
&
: bitwise "and" -
|
: bitwise "or" -
^
: bitwise "xor" (exclusive "or") -
~
: bitwise negation
Bitwise operators can be applied on arguments which are of type byte
, short
, int
, long
, or BigInteger
.
If one of the arguments is a BigInteger
, the result will be of type BigInteger
;
otherwise, if one of the arguments is a long
, the result will be of type long
;
otherwise, the result will be of type int
:
int a = 0b00101010
assert a == 42
int b = 0b00001000
assert b == 8
assert (a & a) == a (1)
assert (a & b) == b (2)
assert (a | a) == a (3)
assert (a | b) == a (4)
int mask = 0b11111111 (5)
assert ((a ^ a) & mask) == 0b00000000 (6)
assert ((a ^ b) & mask) == 0b00100010 (7)
assert ((~a) & mask) == 0b11010101 (8)
1 | bitwise and |
2 | bitwise and returns common bits |
3 | bitwise or |
4 | bitwise or returns all '1' bits |
5 | setting a mask to check only the last 8 bits |
6 | bitwise exclusive or on self returns 0 |
7 | bitwise exclusive or |
8 | bitwise negation |
It’s worth noting that the internal representation of primitive types follow the Java Language Specification. In particular, primitive types are signed, meaning that for a bitwise negation, it is always good to use a mask to retrieve only the necessary bits.
In Groovy, bitwise operators are overloadable, meaning that you can define the behavior of those operators for any kind of object.
4.2. Bit shift operators
Groovy offers three bit shift operators:
-
<<
: left shift -
>>
: right shift -
>>>
: right shift unsigned
All three operators are applicable where the left argument is of type byte
, short
, int
, or long
.
The first two operators can also be applied where the left argument is of type BigInteger
.
If the left argument is a BigInteger
, the result will be of type BigInteger
;
otherwise, if the left argument is a long
, the result will be of type long
;
otherwise, the result will be of type int
:
assert 12.equals(3 << 2) (1)
assert 24L.equals(3L << 3) (1)
assert 48G.equals(3G << 4) (1)
assert 4095 == -200 >>> 20
assert -1 == -200 >> 20
assert 2G == 5G >> 1
assert -3G == -5G >> 1
1 | equals method used instead of == to confirm result type |
In Groovy, bit shift operators are overloadable, meaning that you can define the behavior of those operators for any kind of object.
5. Conditional operators
5.1. Not operator
The "not" operator is represented with an exclamation mark (!
) and inverts the result of the underlying boolean expression. In
particular, it is possible to combine the not
operator with the Groovy truth:
assert (!true) == false (1)
assert (!'foo') == false (2)
assert (!'') == true (3)
1 | the negation of true is false |
2 | 'foo' is a non empty string, evaluating to true , so negation returns false |
3 | '' is an empty string, evaluating to false , so negation returns true |
5.2. Ternary operator
The ternary operator is a shortcut expression that is equivalent to an if/else branch assigning some value to a variable.
Instead of:
if (string!=null && string.length()>0) {
result = 'Found'
} else {
result = 'Not found'
}
You can write:
result = (string!=null && string.length()>0) ? 'Found' : 'Not found'
The ternary operator is also compatible with the Groovy truth, so you can make it even simpler:
result = string ? 'Found' : 'Not found'
5.3. Elvis operator
The "Elvis operator" is a shortening of the ternary operator. One instance of where this is handy is for returning
a 'sensible default' value if an expression resolves to false
-ish (as in
Groovy truth). A simple example might look like this:
displayName = user.name ? user.name : 'Anonymous' (1)
displayName = user.name ?: 'Anonymous' (2)
1 | with the ternary operator, you have to repeat the value you want to assign |
2 | with the Elvis operator, the value, which is tested, is used if it is not false -ish |
Usage of the Elvis operator reduces the verbosity of your code and reduces the risks of errors in case of refactorings, by removing the need to duplicate the expression which is tested in both the condition and the positive return value.
5.4. Elvis assignment operator
Groovy 3.0.0 introduces the Elvis operator, for example:
import groovy.transform.ToString
@ToString
class Element {
String name
int atomicNumber
}
def he = new Element(name: 'Helium')
he.with {
name = name ?: 'Hydrogen' // existing Elvis operator
atomicNumber ?= 2 // new Elvis assignment shorthand
}
assert he.toString() == 'Element(Helium, 2)'
6. Object operators
6.1. Safe navigation operator
The Safe Navigation operator is used to avoid a NullPointerException
. Typically when you have a reference to an object
you might need to verify that it is not null
before accessing methods or properties of the object. To avoid this, the safe
navigation operator will simply return null
instead of throwing an exception, like so:
def person = Person.find { it.id == 123 } (1)
def name = person?.name (2)
assert name == null (3)
1 | find will return a null instance |
2 | use of the null-safe operator prevents from a NullPointerException |
3 | result is null |
6.2. Direct field access operator
Normally in Groovy, when you write code like this:
class User {
public final String name (1)
User(String name) { this.name = name}
String getName() { "Name: $name" } (2)
}
def user = new User('Bob')
assert user.name == 'Name: Bob' (3)
1 | public field name |
2 | a getter for name that returns a custom string |
3 | calls the getter |
The user.name
call triggers a call to the property of the same name, that is to say, here, to the getter for name
. If
you want to retrieve the field instead of calling the getter, you can use the direct field access operator:
assert user.@name == 'Bob' (1)
1 | use of .@ forces usage of the field instead of the getter |
6.3. Method pointer operator
The method pointer operator (.&
) can be used to store a reference to a method in a variable,
in order to call it later:
def str = 'example of method reference' (1)
def fun = str.&toUpperCase (2)
def upper = fun() (3)
assert upper == str.toUpperCase() (4)
1 | the str variable contains a String |
2 | we store a reference to the toUpperCase method on the str instance inside a variable named fun |
3 | fun can be called like a regular method |
4 | we can check that the result is the same as if we had called it directly on str |
There are multiple advantages in using method pointers. First of all, the type of such a method pointer is
a groovy.lang.Closure
, so it can be used in any place a closure would be used. In particular, it is suitable to
convert an existing method for the needs of the strategy pattern:
def transform(List elements, Closure action) { (1)
def result = []
elements.each {
result << action(it)
}
result
}
String describe(Person p) { (2)
"$p.name is $p.age"
}
def action = this.&describe (3)
def list = [
new Person(name: 'Bob', age: 42),
new Person(name: 'Julia', age: 35)] (4)
assert transform(list, action) == ['Bob is 42', 'Julia is 35'] (5)
1 | the transform method takes each element of the list and calls the action closure on them, returning a new list |
2 | we define a function that takes a Person and returns a String |
3 | we create a method pointer on that function |
4 | we create the list of elements we want to collect the descriptors |
5 | the method pointer can be used where a Closure was expected |
Method pointers are bound by the receiver and a method name. Arguments are resolved at runtime, meaning that if you have multiple methods with the same name, the syntax is not different, only resolution of the appropriate method to be called will be done at runtime:
def doSomething(String str) { str.toUpperCase() } (1)
def doSomething(Integer x) { 2*x } (2)
def reference = this.&doSomething (3)
assert reference('foo') == 'FOO' (4)
assert reference(123) == 246 (5)
1 | define an overloaded doSomething method accepting a String as an argument |
2 | define an overloaded doSomething method accepting an Integer as an argument |
3 | create a single method pointer on doSomething , without specifying argument types |
4 | using the method pointer with a String calls the String version of doSomething |
5 | using the method pointer with an Integer calls the Integer version of doSomething |
To align with Java 8 method reference expectations, in Groovy 3 and above, you can use new
as the
method name to obtain a method pointer to the constructor:
def foo = BigInteger.&new
def fortyTwo = foo('42')
assert fortyTwo == 42G
Also in Groovy 3 and above, you can obtain a method pointer to an instance method of a class. This method pointer takes an additional parameter being the receiver instance to invoke the method on:
def instanceMethod = String.&toUpperCase
assert instanceMethod('foo') == 'FOO'
For backwards compatibility, any static methods that happen to have the correct parameters for the call will be given precedence over instance methods for this case.
6.4. Method reference operator
The Parrot parser in Groovy 3+ supports the Java 8+ method reference operator.
The method reference operator (::
) can be used to reference a method or constructor
in contexts expecting a functional interface. This overlaps somewhat with the functionality
provided by Groovy’s method pointer operator. Indeed, for dynamic Groovy, the method
reference operator is just an alias for the method pointer operator.
For static Groovy, the operator results in bytecode similar to the bytecode
that Java would produce for the same context.
Some examples highlighting various supported method reference cases are shown in the following script:
import groovy.transform.CompileStatic
import static java.util.stream.Collectors.toList
@CompileStatic
void methodRefs() {
assert 6G == [1G, 2G, 3G].stream().reduce(0G, BigInteger::add) (1)
assert [4G, 5G, 6G] == [1G, 2G, 3G].stream().map(3G::add).collect(toList()) (2)
assert [1G, 2G, 3G] == [1L, 2L, 3L].stream().map(BigInteger::valueOf).collect(toList()) (3)
assert [1G, 2G, 3G] == [1L, 2L, 3L].stream().map(3G::valueOf).collect(toList()) (4)
}
methodRefs()
1 | class instance method reference: add(BigInteger val) is an instance method in BigInteger |
2 | object instance method reference: add(BigInteger val) is an instance method for object 3G |
3 | class static method reference: valueOf(long val) is a static method for class BigInteger |
4 | object static method reference: valueOf(long val) is a static method for object 3G (some consider this bad style in normal circumstances) |
Some examples highlighting various supported constructor reference cases are shown in the following script:
@CompileStatic
void constructorRefs() {
assert [1, 2, 3] == ['1', '2', '3'].stream().map(Integer::new).collect(toList()) (1)
def result = [1, 2, 3].stream().toArray(Integer[]::new) (2)
assert result instanceof Integer[]
assert result.toString() == '[1, 2, 3]'
}
constructorRefs()
1 | class constructor reference |
2 | array constructor reference |
7. Regular expression operators
7.1. Pattern operator
The pattern operator (~
) provides a simple way to create a java.util.regex.Pattern
instance:
def p = ~/foo/
assert p instanceof Pattern
while in general, you find the pattern operator with an expression in a slashy-string, it can be used with any kind of
String
in Groovy:
p = ~'foo' (1)
p = ~"foo" (2)
p = ~$/dollar/slashy $ string/$ (3)
p = ~"${pattern}" (4)
1 | using single quote strings |
2 | using double quotes strings |
3 | the dollar-slashy string lets you use slashes and the dollar sign without having to escape them |
4 | you can also use a GString! |
While you can use most String forms with the Pattern, Find and Match operators, we recommend using the slashy string most of the time to save having to remember the otherwise needed escaping requirements. |
7.2. Find operator
Alternatively to building a pattern, you can use the find operator =~
to directly create a java.util.regex.Matcher
instance:
def text = "some text to match"
def m = text =~ /match/ (1)
assert m instanceof Matcher (2)
if (!m) { (3)
throw new RuntimeException("Oops, text not found!")
}
1 | =~ creates a matcher against the text variable, using the pattern on the right hand side |
2 | the return type of =~ is a Matcher |
3 | equivalent to calling if (!m.find(0)) |
Since a Matcher
coerces to a boolean
by calling its find
method, the =~
operator is consistent with the simple
use of Perl’s =~
operator, when it appears as a predicate (in if
, ?:
, etc.). When the intent is to iterate over
matches of the specified pattern (in while
, etc.) call find()
directly on the matcher or use the iterator
DGM.
7.3. Match operator
The match operator (==~
) is a slight variation of the find operator, that does not return a Matcher
but a boolean
and requires a strict match of the input string:
m = text ==~ /match/ (1)
assert m instanceof Boolean (2)
if (m) { (3)
throw new RuntimeException("Should not reach that point!")
}
1 | ==~ matches the subject with the regular expression, but match must be strict |
2 | the return type of ==~ is therefore a boolean |
3 | equivalent to calling if (text ==~ /match/) |
7.4. Comparing Find vs Match operators
Typically, the match operator is used when the pattern involves a single exact match, otherwise the find operator might be more useful.
assert 'two words' ==~ /\S+\s+\S+/
assert 'two words' ==~ /^\S+\s+\S+$/ (1)
assert !(' leading space' ==~ /\S+\s+\S+/) (2)
def m1 = 'two words' =~ /^\S+\s+\S+$/
assert m1.size() == 1 (3)
def m2 = 'now three words' =~ /^\S+\s+\S+$/ (4)
assert m2.size() == 0 (5)
def m3 = 'now three words' =~ /\S+\s+\S+/
assert m3.size() == 1 (6)
assert m3[0] == 'now three'
def m4 = ' leading space' =~ /\S+\s+\S+/
assert m4.size() == 1 (7)
assert m4[0] == 'leading space'
def m5 = 'and with four words' =~ /\S+\s+\S+/
assert m5.size() == 2 (8)
assert m5[0] == 'and with'
assert m5[1] == 'four words'
1 | equivalent, but explicit ^ and $ are discouraged since they aren’t needed |
2 | no match because of leading space |
3 | one match |
4 | ^ and $ indicate exact match required |
5 | zero matches |
6 | one match, greedily starting at first word |
7 | one match, ignores leading space |
8 | two matches |
8. Other operators
8.1. Spread operator
The Spread-dot Operator (*.
), often abbreviated to just Spread Operator, is used to invoke an action on all items
of an aggregate object. It is equivalent to calling the action on each item and collecting the result into a list:
class Car {
String make
String model
}
def cars = [
new Car(make: 'Peugeot', model: '508'),
new Car(make: 'Renault', model: 'Clio')] (1)
def makes = cars*.make (2)
assert makes == ['Peugeot', 'Renault'] (3)
1 | build a list of Car items. The list is an aggregate of objects. |
2 | call the spread operator on the list, accessing the make property of each item |
3 | returns a list of strings corresponding to the collection of make items |
The expression cars*.make
is equivalent to cars.collect{ it.make }
.
Groovy’s GPath notation allows a short-cut when the referenced property
isn’t a property of the containing list, in that case it is automatically
spread. In the previously mentioned case, the expression cars.make
can
be used, though retaining the explicit spread-dot operator is often recommended.
The spread operator is null-safe, meaning that if an element of the collection is null,
it will return null instead of throwing a NullPointerException
:
cars = [
new Car(make: 'Peugeot', model: '508'),
null, (1)
new Car(make: 'Renault', model: 'Clio')]
assert cars*.make == ['Peugeot', null, 'Renault'] (2)
assert null*.make == null (3)
1 | build a list for which one of the elements is null |
2 | using the spread operator will not throw a NullPointerException |
3 | the receiver might also be null, in which case the return value is null |
The spread operator can be used on any class which implements the Iterable
interface:
class Component {
Long id
String name
}
class CompositeObject implements Iterable<Component> {
def components = [
new Component(id: 1, name: 'Foo'),
new Component(id: 2, name: 'Bar')]
@Override
Iterator<Component> iterator() {
components.iterator()
}
}
def composite = new CompositeObject()
assert composite*.id == [1,2]
assert composite*.name == ['Foo','Bar']
Use multiple invocations of the spread-dot operator (here cars*.models*.name
) when
working with aggregates of data structures which themselves contain aggregates:
class Make {
String name
List<Model> models
}
@Canonical
class Model {
String name
}
def cars = [
new Make(name: 'Peugeot',
models: [new Model('408'), new Model('508')]),
new Make(name: 'Renault',
models: [new Model('Clio'), new Model('Captur')])
]
def makes = cars*.name
assert makes == ['Peugeot', 'Renault']
def models = cars*.models*.name
assert models == [['408', '508'], ['Clio', 'Captur']]
assert models.sum() == ['408', '508', 'Clio', 'Captur'] // flatten one level
assert models.flatten() == ['408', '508', 'Clio', 'Captur'] // flatten all levels (one in this case)
Consider using the collectNested
DGM method instead of the spread-dot operator for collections of collections:
class Car {
String make
String model
}
def cars = [
[
new Car(make: 'Peugeot', model: '408'),
new Car(make: 'Peugeot', model: '508')
], [
new Car(make: 'Renault', model: 'Clio'),
new Car(make: 'Renault', model: 'Captur')
]
]
def models = cars.collectNested{ it.model }
assert models == [['408', '508'], ['Clio', 'Captur']]
8.1.1. Spreading method arguments
There may be situations when the arguments of a method call can be found in a list that you need to adapt to the method arguments. In such situations, you can use the spread operator to call the method. For example, imagine you have the following method signature:
int function(int x, int y, int z) {
x*y+z
}
then if you have the following list:
def args = [4,5,6]
you can call the method without having to define intermediate variables:
assert function(*args) == 26
It is even possible to mix normal arguments with spread ones:
args = [4]
assert function(*args,5,6) == 26
8.1.2. Spread list elements
When used inside a list literal, the spread operator acts as if the spread element contents were inlined into the list:
def items = [4,5] (1)
def list = [1,2,3,*items,6] (2)
assert list == [1,2,3,4,5,6] (3)
1 | items is a list |
2 | we want to insert the contents of the items list directly into list without having to call addAll |
3 | the contents of items has been inlined into list |
8.1.3. Spread map elements
The spread map operator works in a similar manner as the spread list operator, but for maps. It allows you to inline the contents of a map into another map literal, like in the following example:
def m1 = [c:3, d:4] (1)
def map = [a:1, b:2, *:m1] (2)
assert map == [a:1, b:2, c:3, d:4] (3)
1 | m1 is the map that we want to inline |
2 | we use the *:m1 notation to spread the contents of m1 into map |
3 | map contains all the elements of m1 |
The position of the spread map operator is relevant, like illustrated in the following example:
def m1 = [c:3, d:4] (1)
def map = [a:1, b:2, *:m1, d: 8] (2)
assert map == [a:1, b:2, c:3, d:8] (3)
1 | m1 is the map that we want to inline |
2 | we use the *:m1 notation to spread the contents of m1 into map , but redefine the key d after spreading |
3 | map contains all the expected keys, but d was redefined |
8.2. Range operator
Groovy supports the concept of ranges and provides a notation (..
) to create ranges of objects:
def range = 0..5 (1)
assert (0..5).collect() == [0, 1, 2, 3, 4, 5] (2)
assert (0..<5).collect() == [0, 1, 2, 3, 4] (3)
assert (0..5) instanceof List (4)
assert (0..5).size() == 6 (5)
1 | a simple range of integers, stored into a local variable |
2 | an IntRange , with inclusive bounds |
3 | an IntRange , with exclusive upper bound |
4 | a groovy.lang.Range implements the List interface |
5 | meaning that you can call the size method on it |
Ranges implementation is lightweight, meaning that only the lower and upper bounds are stored. You can create a range
from any Comparable
object that has next()
and previous()
methods to determine the next / previous item in the range.
For example, you can create a range of characters this way:
assert ('a'..'d').collect() == ['a','b','c','d']
8.3. Spaceship operator
The spaceship operator (<=>
) delegates to the compareTo
method:
assert (1 <=> 1) == 0
assert (1 <=> 2) == -1
assert (2 <=> 1) == 1
assert ('a' <=> 'z') == -1
8.4. Subscript operator
The subscript operator is a short hand notation for getAt
or putAt
, depending on whether you find it on
the left hand side or the right hand side of an assignment:
def list = [0,1,2,3,4]
assert list[2] == 2 (1)
list[2] = 4 (2)
assert list[0..2] == [0,1,4] (3)
list[0..2] = [6,6,6] (4)
assert list == [6,6,6,3,4] (5)
1 | [2] can be used instead of getAt(2) |
2 | if on left hand side of an assignment, will call putAt |
3 | getAt also supports ranges |
4 | so does putAt |
5 | the list is mutated |
The subscript operator, in combination with a custom implementation of getAt
/putAt
is a convenient way for destructuring
objects:
class User {
Long id
String name
def getAt(int i) { (1)
switch (i) {
case 0: return id
case 1: return name
}
throw new IllegalArgumentException("No such element $i")
}
void putAt(int i, def value) { (2)
switch (i) {
case 0: id = value; return
case 1: name = value; return
}
throw new IllegalArgumentException("No such element $i")
}
}
def user = new User(id: 1, name: 'Alex') (3)
assert user[0] == 1 (4)
assert user[1] == 'Alex' (5)
user[1] = 'Bob' (6)
assert user.name == 'Bob' (7)
1 | the User class defines a custom getAt implementation |
2 | the User class defines a custom putAt implementation |
3 | create a sample user |
4 | using the subscript operator with index 0 allows retrieving the user id |
5 | using the subscript operator with index 1 allows retrieving the user name |
6 | we can use the subscript operator to write to a property thanks to the delegation to putAt |
7 | and check that it’s really the property name which was changed |
8.5. Safe index operator
Groovy 3.0.0 introduces safe indexing operator, i.e. ?[]
, which is similar to ?.
. For example:
String[] array = ['a', 'b']
assert 'b' == array?[1] // get using normal array index
array?[1] = 'c' // set using normal array index
assert 'c' == array?[1]
array = null
assert null == array?[1] // return null for all index values
array?[1] = 'c' // quietly ignore attempt to set value
assert null == array?[1]
def personInfo = [name: 'Daniel.Sun', location: 'Shanghai']
assert 'Daniel.Sun' == personInfo?['name'] // get using normal map index
personInfo?['name'] = 'sunlan' // set using normal map index
assert 'sunlan' == personInfo?['name']
personInfo = null
assert null == personInfo?['name'] // return null for all map values
personInfo?['name'] = 'sunlan' // quietly ignore attempt to set value
assert null == personInfo?['name']
8.6. Membership operator
The membership operator (in
) is equivalent to calling the isCase
method. In the context of a List
, it is equivalent
to calling contains
, like in the following example:
def list = ['Grace','Rob','Emmy']
assert ('Emmy' in list) (1)
1 | equivalent to calling list.contains('Emmy') or list.isCase('Emmy') |
8.7. Identity operator
In Groovy, using ==
to test equality is different from using the same operator in Java. In Groovy, it is calling equals
.
If you want to compare reference equality, you should use is
like in the following example:
def list1 = ['Groovy 1.8','Groovy 2.0','Groovy 2.3'] (1)
def list2 = ['Groovy 1.8','Groovy 2.0','Groovy 2.3'] (2)
assert list1 == list2 (3)
assert !list1.is(list2) (4)
1 | Create a list of strings |
2 | Create another list of strings containing the same elements |
3 | using == , we test object equality |
4 | but using is , we can check that references are distinct |
8.8. Coercion operator
The coercion operator (as
) is a variant of casting. Coercion converts object from one type to another without them
being compatible for assignment. Let’s take an example:
Integer x = 123
String s = (String) x (1)
1 | Integer is not assignable to a String , so it will produce a ClassCastException at runtime |
This can be fixed by using coercion instead:
Integer x = 123
String s = x as String (1)
1 | Integer is not assignable to a String , but use of as will coerce it to a String |
When an object is coerced into another, unless the target type is the same as the source type, coercion will return a
new object. The rules of coercion differ depending on the source and target types, and coercion may fail if no conversion
rules are found. Custom conversion rules may be implemented thanks to the asType
method:
class Identifiable {
String name
}
class User {
Long id
String name
def asType(Class target) { (1)
if (target == Identifiable) {
return new Identifiable(name: name)
}
throw new ClassCastException("User cannot be coerced into $target")
}
}
def u = new User(name: 'Xavier') (2)
def p = u as Identifiable (3)
assert p instanceof Identifiable (4)
assert !(p instanceof User) (5)
1 | the User class defines a custom conversion rule from User to Identifiable |
2 | we create an instance of User |
3 | we coerce the User instance into an Identifiable |
4 | the target is an instance of Identifiable |
5 | the target is not an instance of User anymore |
8.9. Diamond operator
The diamond operator (<>
) is a syntactic sugar only operator added to support compatibility with the operator of the
same name in Java 7. It is used to indicate that generic types should be inferred from the declaration:
List<String> strings = new LinkedList<>()
In dynamic Groovy, this is totally unused. In statically type checked Groovy, it is also optional since the Groovy type checker performs type inference whether this operator is present or not.
8.10. Call operator
The call operator ()
is used to call a method named call
implicitly. For any object which defines a call
method,
you can omit the .call
part and use the call operator instead:
class MyCallable {
int call(int x) { (1)
2*x
}
}
def mc = new MyCallable()
assert mc.call(2) == 4 (2)
assert mc(2) == 4 (3)
1 | MyCallable defines a method named call . Note that it doesn’t need to implement java.util.concurrent.Callable |
2 | we can call the method using the classic method call syntax |
3 | or we can omit .call thanks to the call operator |
9. Operator precedence
The table below lists all groovy operators in order of precedence.
Level | Operator(s) | Name(s) |
---|---|---|
1 |
|
object creation, explicit parentheses |
|
method call, closure, literal list/map |
|
|
member access, method closure, field/attribute access |
|
|
safe dereferencing, spread, spread-dot, spread-map |
|
|
bitwise negate/pattern, not, typecast |
|
|
list/map/array (safe) index, post inc/decrement |
|
2 |
|
power |
3 |
|
pre inc/decrement, unary plus, unary minus |
4 |
|
multiply, div, remainder |
5 |
|
addition, subtraction |
6 |
|
left/right (unsigned) shift, inclusive/exclusive range |
7 |
|
less/greater than/or equal, in, not in, instanceof, not instanceof, type coercion |
8 |
|
equals, not equals, compare to, identical to, not identical to |
|
regex find, regex match |
|
9 |
|
binary/bitwise and |
10 |
|
binary/bitwise xor |
11 |
|
binary/bitwise or |
12 |
|
logical and |
13 |
|
logical or |
14 |
|
ternary conditional |
|
elvis operator |
|
15 |
|
various assignments |
10. Operator overloading
Groovy allows you to overload the various operators so that they can be used with your own classes. Consider this simple class:
class Bucket {
int size
Bucket(int size) { this.size = size }
Bucket plus(Bucket other) { (1)
return new Bucket(this.size + other.size)
}
}
1 | Bucket implements a special method called plus() |
Just by implementing the plus()
method, the Bucket
class can now be used with the +
operator like so:
def b1 = new Bucket(4)
def b2 = new Bucket(11)
assert (b1 + b2).size == 15 (1)
1 | The two Bucket objects can be added together with the + operator |
All (non-comparator) Groovy operators have a corresponding method that you can implement in your own classes. The only requirements are that your method is public, has the correct name, and has the correct number of arguments. The argument types depend on what types you want to support on the right hand side of the operator. For example, you could support the statement
assert (b1 + 11).size == 15
by implementing the plus()
method with this signature:
Bucket plus(int capacity) {
return new Bucket(this.size + capacity)
}
Here is a complete list of the operators and their corresponding methods:
Operator | Method | Operator | Method |
---|---|---|---|
|
a.plus(b) |
|
a.getAt(b) |
|
a.minus(b) |
|
a.putAt(b, c) |
|
a.multiply(b) |
|
b.isCase(a) |
|
a.div(b) |
|
a.leftShift(b) |
|
a.mod(b) |
|
a.rightShift(b) |
|
a.power(b) |
|
a.rightShiftUnsigned(b) |
|
a.or(b) |
|
a.next() |
|
a.and(b) |
|
a.previous() |
|
a.xor(b) |
|
a.positive() |
|
a.asType(b) |
|
a.negative() |
|
a.call() |
|
a.bitwiseNegate() |