Introduction
Groovy…
-
is an agile and dynamic language for the Java Virtual Machine
-
builds upon the strengths of Java but has additional power features inspired by languages like Python, Ruby and Smalltalk
-
makes modern programming features available to Java developers with almost-zero learning curve
-
provides the ability to statically type check and statically compile your code for robustness and performance
-
supports Domain-Specific Languages and other compact syntax so your code becomes easy to read and maintain
-
makes writing shell and build scripts easy with its powerful processing primitives, OO abilities and an Ant DSL
-
increases developer productivity by reducing scaffolding code when developing web, GUI, database or console applications
-
simplifies testing by supporting unit testing and mocking out-of-the-box
-
seamlessly integrates with all existing Java classes and libraries
-
compiles straight to Java bytecode so you can use it anywhere you can use Java
1. Groovy Language Specification
1.1. Syntax
This chapter covers the syntax of the Groovy programming language. The grammar of the language derives from the Java grammar, but enhances it with specific constructs for Groovy, and allows certain simplifications.
1.1.1. Comments
Single-line comment
Single-line comments start with // and can be found at any position in the line.
The characters following //, until the end of the line, are considered part of the comment.
// a standalone single line comment
println "hello" // a comment till the end of the line
Multiline comment
A multiline comment starts with /* and can be found at any position in the line.
The characters following /* will be considered part of the comment, including new line characters,
up to the first */ closing the comment.
Multiline comments can thus be put at the end of a statement, or even inside a statement.
/* a standalone multiline comment
spanning two lines */
println "hello" /* a multiline comment starting
at the end of a statement */
println 1 /* one */ + 2 /* two */
Groovydoc comment
Similarly to multiline comments, Groovydoc comments are multiline, but start with /** and end with */.
Lines following the first Groovydoc comment line can optionally start with a star *.
Those comments are associated with:
-
type definitions (classes, interfaces, enums, annotations),
-
fields and properties definitions
-
methods definitions
Although the compiler will not complain about Groovydoc comments not being associated with the above language elements, you should prepend those constructs with the comment right before it.
/**
* A Class description
*/
class Person {
/** the name of the person */
String name
/**
* Creates a greeting method for a certain person.
*
* @param otherPerson the person to greet
* @return a greeting message
*/
String greet(String otherPerson) {
"Hello ${otherPerson}"
}
}
Groovydoc follows the same conventions as Java’s own Javadoc. So you’ll be able to use the same tags as with Javadoc.
In addition, Groovy supports Runtime Groovydoc since 3.0.0, i.e. Groovydoc can be retained at runtime.
Runtime Groovydoc is disabled by default. It can be enabled by adding JVM option -Dgroovy.attach.runtime.groovydoc=true
|
The Runtime Groovydoc starts with /**@ and ends with */, for example:
/**@
* Some class groovydoc for Foo
*/
class Foo {
/**@
* Some method groovydoc for bar
*/
void bar() {
}
}
assert Foo.class.groovydoc.content.contains('Some class groovydoc for Foo') (1)
assert Foo.class.getMethod('bar', new Class[0]).groovydoc.content.contains('Some method groovydoc for bar') (2)
| 1 | Get the runtime groovydoc for class Foo |
| 2 | Get the runtime groovydoc for method bar |
Shebang line
Beside the single-line comment, there is a special line comment, often called the shebang line understood by UNIX systems
which allows scripts to be run directly from the command-line, provided you have installed the Groovy distribution
and the groovy command is available on the PATH.
#!/usr/bin/env groovy
println "Hello from the shebang line"
The # character must be the first character of the file. Any indentation would yield a compilation error.
|
1.1.2. Keywords
Groovy has the following reserved keywords:
abstract |
assert |
break |
case |
catch |
class |
const |
continue |
def |
default |
do |
else |
enum |
extends |
final |
finally |
for |
goto |
if |
implements |
import |
instanceof |
interface |
native |
new |
null |
non-sealed |
package |
public |
protected |
private |
return |
static |
strictfp |
super |
switch |
synchronized |
this |
threadsafe |
throw |
throws |
transient |
try |
while |
Of these, const, goto, strictfp, and threadsafe are not currently in use.
The reserved keywords can’t in general be used for variable, field and method names.
In addition, Groovy has the following contextual keywords:
as |
in |
permits |
record |
sealed |
trait |
var |
yields |
These words are only keywords in certain contexts and can be more freely used in some places, in particular for variables, fields and method names.
The restrictions on reserved keywords also apply for the primitive types, the boolean literals and the null literal (all of which are discussed later):
null |
true |
false |
boolean |
char |
byte |
short |
int |
long |
float |
double |
1.1.3. Identifiers
Normal identifiers
Identifiers start with a letter, a dollar or an underscore. They cannot start with a number.
A letter can be in the following ranges:
-
'a' to 'z' (lowercase ascii letter)
-
'A' to 'Z' (uppercase ascii letter)
-
'\u00C0' to '\u00D6'
-
'\u00D8' to '\u00F6'
-
'\u00F8' to '\u00FF'
-
'\u0100' to '\uFFFE'
Then following characters can contain letters and numbers.
Here are a few examples of valid identifiers (here, variable names):
def name
def item3
def with_underscore
def $dollarStart
But the following ones are invalid identifiers:
def 3tier
def a+b
def a#b
All keywords are also valid identifiers when following a dot:
foo.as
foo.assert
foo.break
foo.case
foo.catch
Quoted identifiers
Quoted identifiers appear after the dot of a dotted expression.
For instance, the name part of the person.name expression can be quoted with person."name" or person.'name'.
This is particularly interesting when certain identifiers contain illegal characters that are forbidden by the Java Language Specification,
but which are allowed by Groovy when quoted. For example, characters like a dash, a space, an exclamation mark, etc.
def map = [:]
map."an identifier with a space and double quotes" = "ALLOWED"
map.'with-dash-signs-and-single-quotes' = "ALLOWED"
assert map."an identifier with a space and double quotes" == "ALLOWED"
assert map.'with-dash-signs-and-single-quotes' == "ALLOWED"
As we shall see in the following section on strings, Groovy provides different string literals. All kind of strings are actually allowed after the dot:
map.'single quote'
map."double quote"
map.'''triple single quote'''
map."""triple double quote"""
map./slashy string/
map.$/dollar slashy string/$
There’s a difference between plain character strings and Groovy’s GStrings (interpolated strings), as in that the latter case, the interpolated values are inserted in the final string for evaluating the whole identifier:
def firstname = "Homer"
map."Simpson-${firstname}" = "Homer Simpson"
assert map.'Simpson-Homer' == "Homer Simpson"
1.1.4. Strings
Text literals are represented in the form of chain of characters called strings.
Groovy lets you instantiate java.lang.String objects, as well as GStrings (groovy.lang.GString)
which are also called interpolated strings in other programming languages.
Single-quoted string
Single-quoted strings are a series of characters surrounded by single quotes:
'a single-quoted string'
Single-quoted strings are plain java.lang.String and don’t support interpolation.
|
String concatenation
All the Groovy strings can be concatenated with the + operator:
assert 'ab' == 'a' + 'b'
Triple-single-quoted string
Triple-single-quoted strings are a series of characters surrounded by triplets of single quotes:
'''a triple-single-quoted string'''
Triple-single-quoted strings are plain java.lang.String and don’t support interpolation.
|
Triple-single-quoted strings may span multiple lines. The content of the string can cross line boundaries without the need to split the string in several pieces and without concatenation or newline escape characters:
def aMultilineString = '''line one
line two
line three'''
If your code is indented, for example in the body of the method of a class, your string will contain the whitespace of the indentation.
The Groovy Development Kit contains methods for stripping out the indentation with the String#stripIndent() method,
and with the String#stripMargin() method that takes a delimiter character to identify the text to remove from the beginning of a string.
When creating a string as follows:
def startingAndEndingWithANewline = '''
line one
line two
line three
'''
You will notice that the resulting string contains a newline character as first character. It is possible to strip that character by escaping the newline with a backslash:
def strippedFirstNewline = '''\
line one
line two
line three
'''
assert !strippedFirstNewline.startsWith('\n')
Escaping special characters
You can escape single quotes with the backslash character to avoid terminating the string literal:
'an escaped single quote: \' needs a backslash'
And you can escape the escape character itself with a double backslash:
'an escaped escape character: \\ needs a double backslash'
Some special characters also use the backslash as escape character:
| Escape sequence | Character |
|---|---|
\b |
backspace |
\f |
formfeed |
\n |
newline |
\r |
carriage return |
\s |
single space |
\t |
tabulation |
\\ |
backslash |
\' |
single quote within a single-quoted string (and optional for triple-single-quoted and double-quoted strings) |
\" |
double quote within a double-quoted string (and optional for triple-double-quoted and single-quoted strings) |
We’ll see some more escaping details when it comes to other types of strings discussed later.
Unicode escape sequence
For characters that are not present on your keyboard, you can use unicode escape sequences: a backslash, followed by 'u', then 4 hexadecimal digits.
For example, the Euro currency symbol can be represented with:
'The Euro currency symbol: \u20AC'
Double-quoted string
Double-quoted strings are a series of characters surrounded by double quotes:
"a double-quoted string"
Double-quoted strings are plain java.lang.String if there’s no interpolated expression,
but are groovy.lang.GString instances if interpolation is present.
|
| To escape a double quote, you can use the backslash character: "A double quote: \"". |
String interpolation
Any Groovy expression can be interpolated in all string literals, apart from single and triple-single-quoted strings.
Interpolation is the act of replacing a placeholder in the string with its value upon evaluation of the string.
The placeholder expressions are surrounded by ${}. The curly braces may be omitted for unambiguous dotted expressions,
i.e. we can use just a $ prefix in those cases.
If the GString is ever passed to a method taking a String, the expression value inside the placeholder
is evaluated to its string representation (by calling toString() on that expression) and the resulting
String is passed to the method.
Here, we have a string with a placeholder referencing a local variable:
def name = 'Guillaume' // a plain string
def greeting = "Hello ${name}"
assert greeting.toString() == 'Hello Guillaume'
Any Groovy expression is valid, as we can see in this example with an arithmetic expression:
def sum = "The sum of 2 and 3 equals ${2 + 3}"
assert sum.toString() == 'The sum of 2 and 3 equals 5'
Not only are expressions allowed in between the ${} placeholder, but so are statements. However, a statement’s value is just null.
So if several statements are inserted in that placeholder, the last one should somehow return a meaningful value to be inserted.
For instance, "The sum of 1 and 2 is equal to ${def a = 1; def b = 2; a + b}" is supported and works as expected but a good practice is usually to stick to simple expressions inside GString placeholders.
|
In addition to ${} placeholders, we can also use a lone $ sign prefixing a dotted expression:
def person = [name: 'Guillaume', age: 36]
assert "$person.name is $person.age years old" == 'Guillaume is 36 years old'
But only dotted expressions of the form a.b, a.b.c, etc, are valid. Expressions containing parentheses like method calls,
curly braces for closures, dots which aren’t part of a property expression or arithmetic operators would be invalid.
Given the following variable definition of a number:
def number = 3.14
The following statement will throw a groovy.lang.MissingPropertyException because Groovy believes you’re trying to access the toString property of that number, which doesn’t exist:
shouldFail(MissingPropertyException) {
println "$number.toString()"
}
You can think of "$number.toString()" as being interpreted by the parser as "${number.toString}()".
|
Similarly, if the expression is ambiguous, you need to keep the curly braces:
String thing = 'treasure'
assert 'The x-coordinate of the treasure is represented by treasure.x' ==
"The x-coordinate of the $thing is represented by $thing.x" // <= Not allowed: ambiguous!!
assert 'The x-coordinate of the treasure is represented by treasure.x' ==
"The x-coordinate of the $thing is represented by ${thing}.x" // <= Curly braces required
If you need to escape the $ or ${} placeholders in a GString so they appear as is without interpolation,
you just need to use a \ backslash character to escape the dollar sign:
assert '$5' == "\$5"
assert '${name}' == "\${name}"
Special case of interpolating closure expressions
So far, we’ve seen we could interpolate arbitrary expressions inside the ${} placeholder, but there is a special case and notation for closure expressions. When the placeholder contains an arrow, ${→}, the expression is actually a closure expression — you can think of it as a closure with a dollar prepended in front of it:
def sParameterLessClosure = "1 + 2 == ${-> 3}" (1)
assert sParameterLessClosure == '1 + 2 == 3'
def sOneParamClosure = "1 + 2 == ${ w -> w << 3}" (2)
assert sOneParamClosure == '1 + 2 == 3'
| 1 | The closure is a parameterless closure which doesn’t take arguments. |
| 2 | Here, the closure takes a single java.io.StringWriter argument, to which you can append content with the << leftShift operator.
In either case, both placeholders are embedded closures. |
In appearance, it looks like a more verbose way of defining expressions to be interpolated, but closures have an interesting advantage over mere expressions: lazy evaluation.
Let’s consider the following sample:
def number = 1 (1)
def eagerGString = "value == ${number}"
def lazyGString = "value == ${ -> number }"
assert eagerGString == "value == 1" (2)
assert lazyGString == "value == 1" (3)
number = 2 (4)
assert eagerGString == "value == 1" (5)
assert lazyGString == "value == 2" (6)
| 1 | We define a number variable containing 1 that we then interpolate within two GStrings,
as an expression in eagerGString and as a closure in lazyGString. |
| 2 | We expect the resulting string to contain the same string value of 1 for eagerGString. |
| 3 | Similarly for lazyGString |
| 4 | Then we change the value of the variable to a new number |
| 5 | With a plain interpolated expression, the value was actually bound at the time of creation of the GString. |
| 6 | But with a closure expression, the closure is called upon each coercion of the GString into String, resulting in an updated string containing the new number value. |
| An embedded closure expression taking more than one parameter will generate an exception at runtime. Only closures with zero or one parameter are allowed. |
Interoperability with Java
When a method (whether implemented in Java or Groovy) expects a java.lang.String,
but we pass a groovy.lang.GString instance,
the toString() method of the GString is automatically and transparently called.
String takeString(String message) { (4)
assert message instanceof String (5)
return message
}
def message = "The message is ${'hello'}" (1)
assert message instanceof GString (2)
def result = takeString(message) (3)
assert result instanceof String
assert result == 'The message is hello'
| 1 | We create a GString variable |
| 2 | We double-check it’s an instance of the GString |
| 3 | We then pass that GString to a method taking a String as parameter |
| 4 | The signature of the takeString() method explicitly says its sole parameter is a String |
| 5 | We also verify that the parameter is indeed a String and not a GString. |
GString and String hashCodes
Although interpolated strings can be used in lieu of plain Java strings, they differ with strings in a particular way: their hashCodes are different. Plain Java strings are immutable, whereas the resulting String representation of a GString can vary, depending on its interpolated values. Even for the same resulting string, GStrings and Strings don’t have the same hashCode.
assert "one: ${1}".hashCode() != "one: 1".hashCode()
GString and Strings having different hashCode values, using GString as Map keys should be avoided, especially if we try to retrieve an associated value with a String instead of a GString.
def key = "a"
def m = ["${key}": "letter ${key}"] (1)
assert m["a"] == null (2)
| 1 | The map is created with an initial pair whose key is a GString |
| 2 | When we try to fetch the value with a String key, we will not find it, as Strings and GString have different hashCode values |
Triple-double-quoted string
Triple-double-quoted strings behave like double-quoted strings, with the addition that they are multiline, like the triple-single-quoted strings.
def name = 'Groovy'
def template = """
Dear Mr ${name},
You're the winner of the lottery!
Yours sincerly,
Dave
"""
assert template.toString().contains('Groovy')
| Neither double quotes nor single quotes need be escaped in triple-double-quoted strings. |
Slashy string
Beyond the usual quoted strings, Groovy offers slashy strings, which use / as the opening and closing delimiter.
Slashy strings are particularly useful for defining regular expressions and patterns,
as there is no need to escape backslashes.
Example of a slashy string:
def fooPattern = /.*foo.*/
assert fooPattern == '.*foo.*'
Only forward slashes need to be escaped with a backslash:
def escapeSlash = /The character \/ is a forward slash/
assert escapeSlash == 'The character / is a forward slash'
Slashy strings are multiline:
def multilineSlashy = /one
two
three/
assert multilineSlashy.contains('\n')
Slashy strings can be thought of as just another way to define a GString but with different escaping rules. They hence support interpolation:
def color = 'blue'
def interpolatedSlashy = /a ${color} car/
assert interpolatedSlashy == 'a blue car'
Special cases
An empty slashy string cannot be represented with a double forward slash, as it’s understood by the Groovy parser as a line comment. That’s why the following assert would actually not compile as it would look like a non-terminated statement:
assert '' == //
As slashy strings were mostly designed to make regexp easier so a few things that
are errors in GStrings like $() or $5 will work with slashy strings.
Remember that escaping backslashes is not required. An alternative way of thinking of this is
that in fact escaping is not supported. The slashy string /\t/ won’t contain a tab but instead
a backslash followed by the character 't'. Escaping is only allowed for the slash character, i.e. /\/folder/
will be a slashy string containing '/folder'. A consequence of slash escaping is that a slashy string
can’t end with a backslash. Otherwise that will escape the slashy string terminator.
You can instead use a special trick, /ends with slash ${'\\'}/. But best just avoid using a slashy string in such a case.
Dollar slashy string
Dollar slashy strings are multiline GStrings delimited with an opening $/ and a closing /$.
The escaping character is the dollar sign, and it can escape another dollar, or a forward slash.
Escaping for the dollar and forward slash characters is only needed where conflicts arise with
the special use of those characters. The characters $foo would normally indicate a GString
placeholder, so those four characters can be entered into a dollar slashy string by escaping the dollar, i.e. $$foo.
Similarly, you will need to escape a dollar slashy closing delimiter if you want it to appear in your string.
Here are a few examples:
def name = "Guillaume"
def date = "April, 1st"
def dollarSlashy = $/
Hello $name,
today we're ${date}.
$ dollar sign
$$ escaped dollar sign
\ backslash
/ forward slash
$/ escaped forward slash
$$$/ escaped opening dollar slashy
$/$$ escaped closing dollar slashy
/$
assert [
'Guillaume',
'April, 1st',
'$ dollar sign',
'$ escaped dollar sign',
'\\ backslash',
'/ forward slash',
'/ escaped forward slash',
'$/ escaped opening dollar slashy',
'/$ escaped closing dollar slashy'
].every { dollarSlashy.contains(it) }
It was created to overcome some of the limitations of the slashy string escaping rules. Use it when its escaping rules suit your string contents (typically if it has some slashes you don’t want to escape).
String summary table
String name |
String syntax |
Interpolated |
Multiline |
Escape character |
Single-quoted |
|
|
||
Triple-single-quoted |
|
|
||
Double-quoted |
|
|
||
Triple-double-quoted |
|
|
||
Slashy |
|
|
||
Dollar slashy |
|
|
Characters
Unlike Java, Groovy doesn’t have an explicit character literal. However, you can be explicit about making a Groovy string an actual character, by three different means:
char c1 = 'A' (1)
assert c1 instanceof Character
def c2 = 'B' as char (2)
assert c2 instanceof Character
def c3 = (char)'C' (3)
assert c3 instanceof Character
| 1 | by being explicit when declaring a variable holding the character by specifying the char type |
| 2 | by using type coercion with the as operator |
| 3 | by using a cast to char operation |
| The first option 1 is interesting when the character is held in a variable, while the other two (2 and 3) are more interesting when a char value must be passed as argument of a method call. |
1.1.5. Numbers
Groovy supports different kinds of integral literals and decimal literals, backed by the usual Number types of Java.
Integral literals
The integral literal types are the same as in Java:
-
byte -
char -
short -
int -
long -
java.math.BigInteger
You can create integral numbers of those types with the following declarations:
// primitive types
byte b = 1
char c = 2
short s = 3
int i = 4
long l = 5
// infinite precision
BigInteger bi = 6
If you use optional typing by using the def keyword, the type of the integral number will vary:
it’ll adapt to the capacity of the type that can hold that number.
For positive numbers:
def a = 1
assert a instanceof Integer
// Integer.MAX_VALUE
def b = 2147483647
assert b instanceof Integer
// Integer.MAX_VALUE + 1
def c = 2147483648
assert c instanceof Long
// Long.MAX_VALUE
def d = 9223372036854775807
assert d instanceof Long
// Long.MAX_VALUE + 1
def e = 9223372036854775808
assert e instanceof BigInteger
As well as for negative numbers:
def na = -1
assert na instanceof Integer
// Integer.MIN_VALUE
def nb = -2147483648
assert nb instanceof Integer
// Integer.MIN_VALUE - 1
def nc = -2147483649
assert nc instanceof Long
// Long.MIN_VALUE
def nd = -9223372036854775808
assert nd instanceof Long
// Long.MIN_VALUE - 1
def ne = -9223372036854775809
assert ne instanceof BigInteger
Alternative non-base 10 representations
Numbers can also be represented in binary, octal, hexadecimal and decimal bases.
Binary numbers start with a 0b prefix:
int xInt = 0b10101111
assert xInt == 175
short xShort = 0b11001001
assert xShort == 201 as short
byte xByte = 0b11
assert xByte == 3 as byte
long xLong = 0b101101101101
assert xLong == 2925l
BigInteger xBigInteger = 0b111100100001
assert xBigInteger == 3873g
int xNegativeInt = -0b10101111
assert xNegativeInt == -175
Octal numbers are specified in the typical format of 0 followed by octal digits.
int xInt = 077
assert xInt == 63
short xShort = 011
assert xShort == 9 as short
byte xByte = 032
assert xByte == 26 as byte
long xLong = 0246
assert xLong == 166l
BigInteger xBigInteger = 01111
assert xBigInteger == 585g
int xNegativeInt = -077
assert xNegativeInt == -63
Hexadecimal numbers are specified in the typical format of 0x followed by hex digits.
int xInt = 0x77
assert xInt == 119
short xShort = 0xaa
assert xShort == 170 as short
byte xByte = 0x3a
assert xByte == 58 as byte
long xLong = 0xffff
assert xLong == 65535l
BigInteger xBigInteger = 0xaaaa
assert xBigInteger == 43690g
Double xDouble = new Double('0x1.0p0')
assert xDouble == 1.0d
int xNegativeInt = -0x77
assert xNegativeInt == -119
Decimal literals
The decimal literal types are the same as in Java:
-
float -
double -
java.math.BigDecimal
You can create decimal numbers of those types with the following declarations:
// primitive types
float f = 1.234
double d = 2.345
// infinite precision
BigDecimal bd = 3.456
Decimals can use exponents, with the e or E exponent letter, followed by an optional sign,
and an integral number representing the exponent:
assert 1e3 == 1_000.0
assert 2E4 == 20_000.0
assert 3e+1 == 30.0
assert 4E-2 == 0.04
assert 5e-1 == 0.5
Conveniently for exact decimal number calculations, Groovy chooses java.math.BigDecimal as its decimal number type.
In addition, both float and double are supported, but require an explicit type declaration, type coercion or suffix.
Even if BigDecimal is the default for decimal numbers, such literals are accepted in methods or closures taking float or double as parameter types.
| Decimal numbers can’t be represented using a binary, octal or hexadecimal representation. |
Underscore in literals
When writing long literal numbers, it’s harder on the eye to figure out how some numbers are grouped together, for example with groups of thousands, of words, etc. By allowing you to place underscore in number literals, it’s easier to spot those groups:
long creditCardNumber = 1234_5678_9012_3456L
long socialSecurityNumbers = 999_99_9999L
double monetaryAmount = 12_345_132.12
long hexBytes = 0xFF_EC_DE_5E
long hexWords = 0xFFEC_DE5E
long maxLong = 0x7fff_ffff_ffff_ffffL
long alsoMaxLong = 9_223_372_036_854_775_807L
long bytes = 0b11010010_01101001_10010100_10010010
Number type suffixes
We can force a number (including binary, octals and hexadecimals) to have a specific type by giving a suffix (see table below), either uppercase or lowercase.
| Type | Suffix |
|---|---|
BigInteger |
|
Long |
|
Integer |
|
BigDecimal |
|
Double |
|
Float |
|
Examples:
assert 42I == Integer.valueOf('42')
assert 42i == Integer.valueOf('42') // lowercase i more readable
assert 123L == Long.valueOf("123") // uppercase L more readable
assert 2147483648 == Long.valueOf('2147483648') // Long type used, value too large for an Integer
assert 456G == new BigInteger('456')
assert 456g == new BigInteger('456')
assert 123.45 == new BigDecimal('123.45') // default BigDecimal type used
assert .321 == new BigDecimal('.321')
assert 1.200065D == Double.valueOf('1.200065')
assert 1.234F == Float.valueOf('1.234')
assert 1.23E23D == Double.valueOf('1.23E23')
assert 0b1111L.class == Long // binary
assert 0xFFi.class == Integer // hexadecimal
assert 034G.class == BigInteger // octal
Math operations
Although operators are covered in more detail elsewhere, it’s important to discuss the behavior of math operations and what their resulting types are.
Division and power binary operations aside (covered below),
-
binary operations between
byte,char,shortandintresult inint -
binary operations involving
longwithbyte,char,shortandintresult inlong -
binary operations involving
BigIntegerand any other integral type result inBigInteger -
binary operations involving
BigDecimalwithbyte,char,short,intandBigIntegerresult inBigDecimal -
binary operations between
float,doubleandBigDecimalresult indouble -
binary operations between two
BigDecimalresult inBigDecimal
The following table summarizes those rules:
| byte | char | short | int | long | BigInteger | float | double | BigDecimal | |
|---|---|---|---|---|---|---|---|---|---|
byte |
int |
int |
int |
int |
long |
BigInteger |
double |
double |
BigDecimal |
char |
int |
int |
int |
long |
BigInteger |
double |
double |
BigDecimal |
|
short |
int |
int |
long |
BigInteger |
double |
double |
BigDecimal |
||
int |
int |
long |
BigInteger |
double |
double |
BigDecimal |
|||
long |
long |
BigInteger |
double |
double |
BigDecimal |
||||
BigInteger |
BigInteger |
double |
double |
BigDecimal |
|||||
float |
double |
double |
double |
||||||
double |
double |
double |
|||||||
BigDecimal |
BigDecimal |
Thanks to Groovy’s operator overloading, the usual arithmetic operators work as well with BigInteger and BigDecimal,
unlike in Java where you have to use explicit methods for operating on those numbers.
|
The case of the division operator
The division operators / (and /= for division and assignment) produce a double result
if either operand is a float or double, and a BigDecimal result otherwise
(when both operands are any combination of an integral type short, char, byte, int, long,
BigInteger or BigDecimal).
BigDecimal division is performed with the divide() method if the division is exact
(i.e. yielding a result that can be represented within the bounds of the same precision and scale),
or using a MathContext with a precision
of the maximum of the two operands' precision plus an extra precision of 10,
and a scale
of the maximum of 10 and the maximum of the operands' scale.
For integer division like in Java, you should use the intdiv() method,
as Groovy doesn’t provide a dedicated integer division operator symbol.
|
The case of the power operator
The power operation is represented by the ** operator, with two parameters: the base and the exponent.
The result of the power operation depends on its operands, and the result of the operation
(in particular if the result can be represented as an integral value).
The following rules are used by Groovy’s power operation to determine the resulting type:
-
If the exponent is a decimal value
-
if the result can be represented as an
Integer, then return anInteger -
else if the result can be represented as a
Long, then return aLong -
otherwise return a
Double
-
-
If the exponent is an integral value
-
if the exponent is strictly negative, then return an
Integer,LongorDoubleif the result value fits in that type -
if the exponent is positive or zero
-
if the base is a
BigDecimal, then return aBigDecimalresult value -
if the base is a
BigInteger, then return aBigIntegerresult value -
if the base is an
Integer, then return anIntegerif the result value fits in it, otherwise aBigInteger -
if the base is a
Long, then return aLongif the result value fits in it, otherwise aBigInteger
-
-
We can illustrate those rules with a few examples:
// base and exponent are ints and the result can be represented by an Integer
assert 2 ** 3 instanceof Integer // 8
assert 10 ** 9 instanceof Integer // 1_000_000_000
// the base is a long, so fit the result in a Long
// (although it could have fit in an Integer)
assert 5L ** 2 instanceof Long // 25
// the result can't be represented as an Integer or Long, so return a BigInteger
assert 100 ** 10 instanceof BigInteger // 10e20
assert 1234 ** 123 instanceof BigInteger // 170515806212727042875...
// the base is a BigDecimal and the exponent a negative int
// but the result can be represented as an Integer
assert 0.5 ** -2 instanceof Integer // 4
// the base is an int, and the exponent a negative float
// but again, the result can be represented as an Integer
assert 1 ** -0.3f instanceof Integer // 1
// the base is an int, and the exponent a negative int
// but the result will be calculated as a Double
// (both base and exponent are actually converted to doubles)
assert 10 ** -1 instanceof Double // 0.1
// the base is a BigDecimal, and the exponent is an int, so return a BigDecimal
assert 1.2 ** 10 instanceof BigDecimal // 6.1917364224
// the base is a float or double, and the exponent is an int
// but the result can only be represented as a Double value
assert 3.4f ** 5 instanceof Double // 454.35430372146965
assert 5.6d ** 2 instanceof Double // 31.359999999999996
// the exponent is a decimal value
// and the result can only be represented as a Double value
assert 7.8 ** 1.9 instanceof Double // 49.542708423868476
assert 2 ** 0.1f instanceof Double // 1.0717734636432956
1.1.6. Booleans
Boolean is a special data type that is used to represent truth values: true and false.
Use this data type for simple flags that track true/false conditions.
Boolean values can be stored in variables, assigned into fields, just like any other data type:
def myBooleanVariable = true
boolean untypedBooleanVar = false
booleanField = true
true and false are the only two primitive boolean values.
But more complex boolean expressions can be represented using logical operators.
In addition, Groovy has special rules (often referred to as Groovy Truth) for coercing non-boolean objects to a boolean value.
1.1.7. Lists
Groovy uses a comma-separated list of values, surrounded by square brackets, to denote lists.
Groovy lists are plain JDK java.util.List, as Groovy doesn’t define its own collection classes.
The concrete list implementation used when defining list literals are java.util.ArrayList by default,
unless you decide to specify otherwise, as we shall see later on.
def numbers = [1, 2, 3] (1)
assert numbers instanceof List (2)
assert numbers.size() == 3 (3)
| 1 | We define a list numbers delimited by commas and surrounded by square brackets, and we assign that list into a variable |
| 2 | The list is an instance of Java’s java.util.List interface |
| 3 | The size of the list can be queried with the size() method, and shows our list contains 3 elements |
In the above example, we used a homogeneous list, but you can also create lists containing values of heterogeneous types:
def heterogeneous = [1, "a", true] (1)
| 1 | Our list here contains a number, a string and a boolean value |
We mentioned that by default, list literals are actually instances of java.util.ArrayList,
but it is possible to use a different backing type for our lists,
thanks to using type coercion with the as operator, or with explicit type declaration for your variables:
def arrayList = [1, 2, 3]
assert arrayList instanceof java.util.ArrayList
def linkedList = [2, 3, 4] as LinkedList (1)
assert linkedList instanceof java.util.LinkedList
LinkedList otherLinked = [3, 4, 5] (2)
assert otherLinked instanceof java.util.LinkedList
| 1 | We use coercion with the as operator to explicitly request a java.util.LinkedList implementation |
| 2 | We can say that the variable holding the list literal is of type java.util.LinkedList |
You can access elements of the list with the [] subscript operator (both for reading and setting values)
with positive indices or negative indices to access elements from the end of the list, as well as with ranges,
and use the << leftShift operator to append elements to a list:
def letters = ['a', 'b', 'c', 'd']
assert letters[0] == 'a' (1)
assert letters[1] == 'b'
assert letters[-1] == 'd' (2)
assert letters[-2] == 'c'
letters[2] = 'C' (3)
assert letters[2] == 'C'
letters << 'e' (4)
assert letters[ 4] == 'e'
assert letters[-1] == 'e'
assert letters[1, 3] == ['b', 'd'] (5)
assert letters[2..4] == ['C', 'd', 'e'] (6)
| 1 | Access the first element of the list (zero-based counting) |
| 2 | Access the last element of the list with a negative index: -1 is the first element from the end of the list |
| 3 | Use an assignment to set a new value for the third element of the list |
| 4 | Use the << leftShift operator to append an element at the end of the list |
| 5 | Access two elements at once, returning a new list containing those two elements |
| 6 | Use a range to access a range of values from the list, from a start to an end element position |
As lists can be heterogeneous in nature, lists can also contain other lists to create multidimensional lists:
def multi = [[0, 1], [2, 3]] (1)
assert multi[1][0] == 2 (2)
| 1 | Define a list of numbers |
| 2 | Access the second element of the top-most list, and the first element of the inner list |
1.1.8. Arrays
Groovy reuses the list notation for arrays, but to make such literals arrays, you need to explicitly define the type of the array through coercion or type declaration.
String[] arrStr = ['Ananas', 'Banana', 'Kiwi'] (1)
assert arrStr instanceof String[] (2)
assert !(arrStr instanceof List)
def numArr = [1, 2, 3] as int[] (3)
assert numArr instanceof int[] (4)
assert numArr.size() == 3
| 1 | Define an array of strings using explicit variable type declaration |
| 2 | Assert that we created an array of strings |
| 3 | Create an array of ints with the as operator |
| 4 | Assert that we created an array of primitive ints |
You can also create multi-dimensional arrays:
def matrix3 = new Integer[3][3] (1)
assert matrix3.size() == 3
Integer[][] matrix2 (2)
matrix2 = [[1, 2], [3, 4]]
assert matrix2 instanceof Integer[][]
| 1 | You can define the bounds of a new array |
| 2 | Or declare an array without specifying its bounds |
Access to elements of an array follows the same notation as for lists:
String[] names = ['Cédric', 'Guillaume', 'Jochen', 'Paul']
assert names[0] == 'Cédric' (1)
names[2] = 'Blackdrag' (2)
assert names[2] == 'Blackdrag'
| 1 | Retrieve the first element of the array |
| 2 | Set the value of the third element of the array to a new value |
Java-style array initialization
Groovy has always supported literal list/array definitions using square brackets and has avoided Java-style curly braces so as not to conflict with closure definitions. In the case where the curly braces come immediately after an array type declaration however, there is no ambiguity with closure definitions, so Groovy 3 and above support that variant of the Java array initialization expression.
Examples:
def primes = new int[] {2, 3, 5, 7, 11}
assert primes.size() == 5 && primes.sum() == 28
assert primes.class.name == '[I'
def pets = new String[] {'cat', 'dog'}
assert pets.size() == 2 && pets.sum() == 'catdog'
assert pets.class.name == '[Ljava.lang.String;'
// traditional Groovy alternative still supported
String[] groovyBooks = [ 'Groovy in Action', 'Making Java Groovy' ]
assert groovyBooks.every{ it.contains('Groovy') }
1.1.9. Maps
Sometimes called dictionaries or associative arrays in other languages, Groovy features maps. Maps associate keys to values, separating keys and values with colons, and each key/value pairs with commas, and the whole keys and values surrounded by square brackets.
def colors = [red: '#FF0000', green: '#00FF00', blue: '#0000FF'] (1)
assert colors['red'] == '#FF0000' (2)
assert colors.green == '#00FF00' (3)
colors['pink'] = '#FF00FF' (4)
colors.yellow = '#FFFF00' (5)
assert colors.pink == '#FF00FF'
assert colors['yellow'] == '#FFFF00'
assert colors instanceof java.util.LinkedHashMap
| 1 | We define a map of string color names, associated with their hexadecimal-coded html colors |
| 2 | We use the subscript notation to check the content associated with the red key |
| 3 | We can also use the property notation to assert the color green’s hexadecimal representation |
| 4 | Similarly, we can use the subscript notation to add a new key/value pair |
| 5 | Or the property notation, to add the yellow color |
| When using names for the keys, we actually define string keys in the map. |
Groovy creates maps that are actually instances of java.util.LinkedHashMap.
|
If you try to access a key which is not present in the map:
assert colors.unknown == null
def emptyMap = [:]
assert emptyMap.anyKey == null
You will retrieve a null result.
In the examples above, we used string keys, but you can also use values of other types as keys:
def numbers = [1: 'one', 2: 'two']
assert numbers[1] == 'one'
Here, we used numbers as keys, as numbers can unambiguously be recognized as numbers, so Groovy will not create a string key like in our previous examples. But consider the case you want to pass a variable in lieu of the key, to have the value of that variable become the key:
def key = 'name'
def person = [key: 'Guillaume'] (1)
assert !person.containsKey('name') (2)
assert person.containsKey('key') (3)
| 1 | The key associated with the 'Guillaume' name will actually be the "key" string, not the value associated with the key variable |
| 2 | The map doesn’t contain the 'name' key |
| 3 | Instead, the map contains a 'key' key |
| You can also pass quoted strings as well as keys: ["name": "Guillaume"]. This is mandatory if your key string isn’t a valid identifier, for example if you wanted to create a string key containing a dash like in: ["street-name": "Main street"]. |
When you need to pass variable values as keys in your map definitions, you must surround the variable or expression with parentheses:
person = [(key): 'Guillaume'] (1)
assert person.containsKey('name') (2)
assert !person.containsKey('key') (3)
| 1 | This time, we surround the key variable with parentheses, to instruct the parser we are passing a variable rather than defining a string key |
| 2 | The map does contain the name key |
| 3 | But the map doesn’t contain the key key as before |
1.2. Operators
This chapter covers the operators of the Groovy programming language.
1.2.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.
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
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.
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
1.2.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
1.2.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 |
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 |
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 |
1.2.4. Bitwise and bit shift operators
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.
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.
1.2.5. Conditional operators
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 |
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'
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.
Elvis assignment operator
Groovy 3.0.0 introduces the Elvis operator, for example:
import groovy.transform.ToString
@ToString(includePackage = false)
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)'
1.2.6. Object operators
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 |
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 |
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.
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::valueOf).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 |
1.2.7. Regular expression operators
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. |
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.
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/) |
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 |
1.2.8. Other operators
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 {
Integer 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']]
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
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 |
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 |
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).collect() == [1, 2, 3, 4, 5] (4)
assert (0<..<5).collect() == [1, 2, 3, 4] (5)
assert (0..5) instanceof List (6)
assert (0..5).size() == 6 (7)
| 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 | an IntRange, with exclusive lower bound |
| 5 | an IntRange, with exclusive lower and upper bounds |
| 6 | a groovy.lang.Range implements the List interface |
| 7 | 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']
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
Subscript operator
The subscript operator is a shorthand 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 |
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']
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)
assert ('Alex' !in list) (2)
| 1 | equivalent to calling list.contains('Emmy') or list.isCase('Emmy') |
| 2 | membership negation equivalent to calling !list.contains('Emmy') or !list.isCase('Emmy') |
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)
assert list1 !== list2 (5)
| 1 | Create a list of strings |
| 2 | Create another list of strings containing the same elements |
| 3 | using ==, we test object equality, equivalent to list1.equals(list2) in Java |
| 4 | using is, we can check that references are distinct, equivalent to list1 == list2 in Java |
| 5 | using === or !== (supported and recommended since Groovy 3.0.0), we can also check whether references are distinct or not, equivalent to list1 == list2 and list1 != list2 in Java |
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:
String input = '42'
Integer num = (Integer) input (1)
| 1 | String is not assignable to an Integer, so it will produce a ClassCastException at runtime |
This can be fixed by using coercion instead:
String input = '42'
Integer num = input as Integer (1)
| 1 | String is not assignable to an Integer, but use of as will coerce it to an Integer |
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 |
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.
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 |
1.2.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 ranges |
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 |
13.5 |
|
logical implication |
14 |
|
ternary conditional |
|
elvis operator |
|
15 |
|
various assignments |
1.2.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() |
1.3. Program structure
This chapter covers the program structure of the Groovy programming language.
1.3.1. Package names
Package names play exactly the same role as in Java. They allow us to separate the code base without any conflicts. Groovy classes must specify their package before the class definition, else the default package is assumed.
Defining a package is very similar to Java:
// defining a package named com.yoursite
package com.yoursite
To refer to some class Foo in the com.yoursite.com package you will need to use the fully qualified name com.yoursite.com.Foo, or else you can use an import statement as we’ll see below.
1.3.2. Imports
In order to refer to any class you need a qualified reference to its package. Groovy follows Java’s notion of allowing import statement to resolve class references.
For example, Groovy provides several builder classes, such as MarkupBuilder. MarkupBuilder is inside the package groovy.xml so in order to use this class, you need to import it as shown:
// importing the class MarkupBuilder
import groovy.xml.MarkupBuilder
// using the imported class to create an object
def xml = new MarkupBuilder()
assert xml != null
Default imports
Default imports are the imports that Groovy language provides by default. For example look at the following code:
long time = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)
Date date = new Date(time)
The same code in Java needs an import statement to Date class like this: import java.util.Date. Groovy by default imports these classes for you.
The below imports are added by groovy for you:
import java.lang.*
import java.util.*
import java.io.*
import java.net.*
import java.time.*
import groovy.lang.*
import groovy.util.*
import java.math.BigInteger
import java.math.BigDecimal
This is done because the classes from these packages are most commonly used. By importing these boilerplate code is reduced.
Simple import
A simple import is an import statement where you fully define the class name along with the package. For example the import statement import groovy.xml.MarkupBuilder in the code below is a simple import which directly refers to a class inside a package.
// importing the class MarkupBuilder
import groovy.xml.MarkupBuilder
// using the imported class to create an object
def xml = new MarkupBuilder()
assert xml != null
Star import
Groovy, like Java, provides a special way to import all classes from a package using *, the so-called on-demand or star import. MarkupBuilder is a class which is in package groovy.xml, alongside another class called StreamingMarkupBuilder. In case you need to use both classes, you can do:
import groovy.xml.MarkupBuilder
import groovy.xml.StreamingMarkupBuilder
def markupBuilder = new MarkupBuilder()
assert markupBuilder != null
assert new StreamingMarkupBuilder() != null
That’s perfectly valid code. But with a * import, we can achieve the same effect with just one line. The star imports all the classes under package groovy.xml:
import groovy.xml.*
def markupBuilder = new MarkupBuilder()
assert markupBuilder != null
assert new StreamingMarkupBuilder() != null
One problem with * imports is that they can clutter your local namespace. But with the kinds of aliasing provided by Groovy, this can be solved easily.
Static import
Groovy’s static import capability allows you to reference imported classes as if they were static methods in your own class:
import static java.lang.Boolean.FALSE
assert !FALSE //use directly, without Boolean prefix!
This is similar to Java’s static import capability but is a more dynamic than Java in that it allows you to define methods with the same name as an imported method as long as you have different types:
import static java.lang.String.format (1)
class SomeClass {
String format(Integer i) { (2)
i.toString()
}
static void main(String[] args) {
assert format('String') == 'String' (3)
assert new SomeClass().format(Integer.valueOf(1)) == '1'
}
}
| 1 | static import of method |
| 2 | declaration of method with same name as method statically imported above, but with a different parameter type |
| 3 | compile error in Java, but is valid Groovy code |
If you have the same signature, the imported method takes precedence.
Static import aliasing
Static imports with the as keyword provide an elegant solution to namespace problems. Suppose you want to get a Calendar instance, using its getInstance() method. It’s a static method, so we can use a static import. But instead of calling getInstance() every time, which can be misleading when separated from its class name, we can import it with an alias, to increase code readability:
import static Calendar.getInstance as now
assert now().class == Calendar.getInstance().class
Now, that’s clean!
Static star import
A static star import is very similar to the regular star import. It will import all the static members from the given class.
For example, let’s say we need to calculate sines and cosines for our application. The class java.lang.Math has static methods named sin and cos which fit our need. With the help of a static star import, we can do:
import static java.lang.Math.*
assert sin(0) == 0.0
assert cos(0) == 1.0
As you can see, we were able to access the methods sin and cos directly, without the Math. prefix.
Import aliasing
With type aliasing, we can refer to a fully qualified class name using a name of our choice. This can be done with the as keyword, as before.
For example we can import java.sql.Date as SQLDate and use it in the same file as java.util.Date without having to use the fully qualified name of either class:
import java.util.Date
import java.sql.Date as SQLDate
Date utilDate = new Date(1000L)
SQLDate sqlDate = new SQLDate(1000L)
assert utilDate instanceof java.util.Date
assert sqlDate instanceof java.sql.Date
Namespace conflicts
Similar to Java, it is an error in Groovy to specify multiple imports with the same name but different types:
import java.awt.List
import java.util.List // error: name already declared
And to declare an import and a top-level type with the same name:
import java.util.List
class List { } // error: name already declared
However, inner types can shadow names from the unit scope:
import java.util.List
class Main {
class List { } // allowed; "List" refers to this type within `Main`'s scope and `java.util.List` elsewhere
}
1.3.3. Scripts versus classes
Groovy supports both scripts and classes. From Groovy 5, Groovy also supports JEP 445 compatible scripts.
Motivation for scripts
Take the following code for example:
class Main { (1)
static void main(String... args) { (2)
println 'Groovy world!' (3)
}
}
| 1 | define a Main class, the name is arbitrary |
| 2 | the public static void main(String[]) method is usable as the main method of the class |
| 3 | the main body of the method |
This is typical code that you would find coming from Java, where code has to be embedded into a class to be executable. Groovy makes it easier, the following code is equivalent:
println 'Groovy world!'
A script can be considered as a class without needing to explicitly declare it.
There are some differences which we’ll cover next. First, we’ll cover Groovy’s
main Script class. Then, we’ll cover JEP 445 compatible classes.
Script class
A groovy.lang.Script is always compiled into a class. The Groovy compiler will compile the class for you,
with the body of the script copied into a run method. The previous example is therefore compiled as if it was the
following:
import org.codehaus.groovy.runtime.InvokerHelper
class Main extends Script { (1)
def run() { (2)
println 'Groovy world!' (3)
}
static void main(String[] args) { (4)
InvokerHelper.runScript(Main, args) (5)
}
}
| 1 | The Main class extends the groovy.lang.Script class |
| 2 | groovy.lang.Script requires a run method returning a value |
| 3 | the script body goes into the run method |
| 4 | the main method is automatically generated |
| 5 | and delegates the execution of the script on the run method |
If the script is in a file, then the base name of the file is used to determine the name of the generated script class.
In this example, if the name of the file is Main.groovy, then the script class is going to be Main.
Methods
It is possible to define methods into a script, as illustrated here:
int fib(int n) {
n < 2 ? 1 : fib(n-1) + fib(n-2)
}
assert fib(10)==89
You can also mix methods and code. The generated script class will carry all methods into the script class, and
assemble all script bodies into the run method:
println 'Hello' (1)
int power(int n) { 2**n } (2)
println "2^6==${power(6)}" (3)
| 1 | script begins |
| 2 | a method is defined within the script body |
| 3 | and script continues |
Statements 1 and 3 are sometimes referred to as "loose" statements.
They are not contained within an explicit enclosing method or class.
Loose statements are assembled sequentially into the run method.
So, the above code is internally converted into:
import org.codehaus.groovy.runtime.InvokerHelper
class Main extends Script {
int power(int n) { 2** n} (1)
def run() {
println 'Hello' (2)
println "2^6==${power(6)}" (3)
}
static void main(String[] args) {
InvokerHelper.runScript(Main, args)
}
}
| 1 | the power method is copied as-is into the generated script class |
| 2 | first statement is copied into the run method |
| 3 | second statement is copied into the run method |
| Even if Groovy creates a class from your script, it is totally transparent for the user. In particular, scripts are compiled to bytecode, and line numbers are preserved. This implies that if an exception is thrown in a script, the stack trace will show line numbers corresponding to the original script, not the generated code that we have shown. |
Variables
Variables in a script do not require a type definition. This means that this script:
int x = 1
int y = 2
assert x+y == 3
will behave the same as:
x = 1
y = 2
assert x+y == 3
However, there is a semantic difference between the two:
-
if the variable is declared as in the first example, it is a local variable. It will be declared in the
runmethod that the compiler will generate and will not be visible outside of the script main body. In particular, such a variable will not be visible in other methods of the script -
if the variable is undeclared, it goes into the groovy.lang.Script#getBinding(). The binding is visible from the methods, and is especially important if you use a script to interact with an application and need to share data between the script and the application. Readers might refer to the integration guide for more information.
Another approach to making a variable visible to all methods, is to use the
@Field annotation.
A variable annotated this way will become a field of the generated script class and,
as for local variables, access won’t involve the script Binding.
If you have a local variable or script field with the same name as a binding variable,
we recommend renaming one of them to avoid potential confusion. If that’s not possible,
you can use binding.varName to access the binding variable.
|
Convenience variations
As mentioned previously, normally, public static void main and run methods
are automatically added to your script, so it is normally illegal to add your own versions
of either of those; you would see a duplicate method compiler error if you tried.
However, there are some exceptions where the above rules don’t apply.
If your script contains only a compatible main method and no other loose statements,
or only a no-arg run instance method (from Groovy 5), then it is allowed.
In this case, no loose statements (because there aren’t any) are collected into the run method.
The method you supplied is used instead of Groovy adding the respective method(s).
This can be useful if you need to add an annotation to the otherwise implicitly added
main or run methods as this example shows:
@CompileStatic
static main(args) {
println 'Groovy world!'
}
To be recognised as a convenience variation, as well as having no loose statements,
the parameter for the main method should be:
-
untyped as above (
Objecttype), -
or of type
String[], -
or have no arguments (from Groovy 5).
From Groovy 5, a no-arg instance run variant is also supported.
This also allows annotations to be added.
The run variant follows the JEP 445 rules for field declarations
(hence doesn’t need to use the @Field annotation)
as this example involving Jackson JSON serialization shows:
@JsonIgnoreProperties(["binding"])
def run() {
var mapper = new ObjectMapper()
assert mapper.writeValueAsString(this) == '{"pets":["cat","dog"]}'
}
public pets = ['cat', 'dog']
The run variant is recommended if you need your script to extend the Script class
and have access to the script context and bindings. If you don’t have that requirement,
providing one of the main variants will create a JEP 445 compatible class which won’t
extend Script. We’ll cover JEP 445 compatible scripts in more detail next.
1.3.4. JEP 445 compatible scripts
From Groovy 5, support has been added for JEP 445 compatible scripts containing
a main method. Such scripts have several differences to normal Groovy Script classes:
-
they won’t have a
public static void mainmethod added -
they won’t extend the
Scriptclass and hence won’t have access to the script context or binding variables -
allows additional class-level fields and methods to be defined in addition to
main -
can’t have "loose" statements outside the
mainmethod (excluding any field definitions)
A simple example might look like:
void main(args) {
println new Date()
}
An example with additional fields and methods might look like:
def main() {
assert upper(foo) + lower(bar) == 'FOObar'
}
def upper(s) { s.toUpperCase() }
def lower = String::toLowerCase
def (foo, bar) = ['Foo', 'Bar'] (1)
| 1 | Note that multi-assignment syntax is supported and results in separate field definitions for each component. |
Differences with Java JEP 445 behavior
There are some differences with Groovy’s JEP 445 support and that offered by Java:
-
Java supports either a no-arg
mainmethod or one containing a singleString[]parameter. Groovy also adds support for a single untyped (Object) parameter, e.g.def main(args) { … }. This addition is known by the Groovy runner but would not be known by the Java launch protocol for a JDK supporting JEP 445. -
Java supports
voidmain methods. Groovy also adds support for untypeddef(Object) methods, e.g.def main(…)as well asvoid main(…). This addition is known by the Groovy runner but would not be known by the Java launch protocol for a JDK supporting JEP 445. -
For static
mainvariants, Groovy promotes the no-arg or untyped variants to have the standardpublic static void main(String[] args)signature. This is for compatibility with versions of Groovy prior to Groovy 5 (where JEP 445 support was added). As a consequence, such classes are compatible with the Java launch protocol prior to JEP 445 support. -
Groovy’s runner has been made aware of JEP 445 compatible classes and can run all variations for JDK11 and above and without the need for preview mode to be enabled.
1.4. Object orientation
This chapter covers the object-oriented aspects of the Groovy programming language.
1.4.1. Types
Primitive types
Groovy supports the same primitive types as defined by the Java Language Specification:
-
integral types:
byte(8 bit),short(16 bit),int(32 bit) andlong(64 bit) -
floating-point types:
float(32 bit) anddouble(64 bit) -
the
booleantype (one oftrueorfalse) -
the
chartype (16 bit, usable as a numeric type, representing a UTF-16 code)
Also like Java, Groovy uses the respective wrapper classes when objects corresponding to any of the primitive types are required:
| Primitive type | Wrapper class |
|---|---|
boolean |
Boolean |
char |
Character |
short |
Short |
int |
Integer |
long |
Long |
float |
Float |
double |
Double |
Automatic boxing and unboxing occur when, for instance, calling a method requiring the wrapper class and passing it a primitive variable as the parameter, or vice-versa. This is similar to Java but Groovy takes the idea further.
In most scenarios, you can treat a primitive just like it was the full object wrapper equivalent.
For instance, you can call .toString() or .equals(other) on a primitive.
Groovy autowraps and unwraps between references and primitives as needed.
Here’s an example using int which is declared as a static field in a class (discussed shortly):
class Foo {
static int i
}
assert Foo.class.getDeclaredField('i').type == int.class (1)
assert Foo.i.class != int.class && Foo.i.class == Integer.class (2)
| 1 | Primitive type is respected in the bytecode |
| 2 | Looking at the field at runtime shows it has been autowrapped |
Now you may be concerned that this means every time you use a mathematical operator on a reference to a primitive that you’ll incur the cost of unboxing and reboxing the primitive. But this is not the case, as Groovy will compile your operators into their method equivalents and uses those instead. Additionally, Groovy will automatically unbox to a primitive when calling a Java method that takes a primitive parameter and automatically box primitive method return values from Java. However, be aware there are some differences from Java’s method resolution.
Reference Types
Apart from primitives, everything else is an object and has an associated class defining its type. We’ll discuss classes, and class-related or class-like things like interfaces, traits and records shortly.
We might declare two variables, of type String and List, as follows:
String movie = 'The Matrix'
List actors = ['Keanu Reeves', 'Hugo Weaving']
Generics
Groovy carries across the same concepts with regard to generics as Java. When defining classes and methods, it is possible to use a type parameter and create a generic class, interface, method or constructor.
Usage of generic classes and methods, regardless of whether they are defined in Java or Groovy, may involve supplying a type argument.
We might declare a variable, of type "list of string", as follows:
List<String> roles = ['Trinity', 'Morpheus']
Java employs type erasure for backwards compatibility with earlier versions of Java. Dynamic Groovy can be thought of as more aggressively applying type erasure. In general, less generics type information will be checked at compile time. Groovy’s static nature employs similar checks to Java with regard to generics information.
1.4.2. Classes
Groovy classes are very similar to Java classes, and are compatible with Java ones at JVM level. They may have methods, fields and properties (think JavaBeans properties but with less boilerplate). Classes and class members can have the same modifiers (public, protected, private, static, etc.) as in Java with some minor differences at the source level which are explained shortly.
The key differences between Groovy classes and their Java counterparts are:
-
Classes or methods with no visibility modifier are automatically public (a special annotation can be used to achieve package private visibility).
-
Fields with no visibility modifier are turned into properties automatically, which results in less verbose code, since explicit getter and setter methods aren’t needed. More on this aspect will be covered in the fields and properties section.
-
Classes do not need to have the same base name as their source file definitions but it is highly recommended in most scenarios (see also the next point about scripts).
-
One source file may contain one or more classes (but if a file contains any code not in a class, it is considered a script). Scripts are just classes with some special conventions and will have the same name as their source file (so don’t include a class definition within a script having the same name as the script source file).
The following code presents an example class.
class Person { (1)
String name (2)
Integer age
def increaseAge(Integer years) { (3)
this.age += years
}
}
| 1 | class beginning, with the name Person |
| 2 | string field and property named name |
| 3 | method definition |
Normal class
Normal classes refer to classes which are top level and concrete. This means they can be instantiated without restrictions from any other classes or scripts. This way, they can only be public (even though the public keyword may be suppressed). Classes are instantiated by calling their constructors, using the new keyword, as in the following snippet.
def p = new Person()
Inner class
Inner classes are defined within another classes. The enclosing class can use the inner class as usual. On the other side, an inner class can access members of its enclosing class, even if they are private. Classes other than the enclosing class are not allowed to access inner classes. Here is an example:
class Outer {
private String privateStr
def callInnerMethod() {
new Inner().methodA() (1)
}
class Inner { (2)
def methodA() {
println "${privateStr}." (3)
}
}
}
| 1 | the inner class is instantiated and its method gets called |
| 2 | inner class definition, inside its enclosing class |
| 3 | even being private, a field of the enclosing class is accessed by the inner class |
There are some reasons for using inner classes:
-
They increase encapsulation by hiding the inner class from other classes, which do not need to know about it. This also leads to cleaner packages and workspaces.
-
They provide a good organization, by grouping classes that are used by only one class.
-
They lead to more maintainable codes, since inner classes are near the classes that use them.
It is common for an inner class to be an implementation of some interface whose method(s) are needed by the outer class. The code below illustrates this typical usage pattern, here being used with threads.
class Outer2 {
private String privateStr = 'some string'
def startThread() {
new Thread(new Inner2()).start()
}
class Inner2 implements Runnable {
void run() {
println "${privateStr}."
}
}
}
Note that the class Inner2 is defined only to provide an implementation of the method run to class Outer2.
Anonymous inner classes help to eliminate verbosity in this case.
That topic is covered shortly.
Groovy 3+ also supports Java syntax for non-static inner class instantiation, for example:
class Computer {
class Cpu {
int coreNumber
Cpu(int coreNumber) {
this.coreNumber = coreNumber
}
}
}
assert 4 == new Computer().new Cpu(4).coreNumber
Anonymous inner class
The earlier example of an inner class (Inner2) can be simplified with an anonymous inner class.
The same functionality can be achieved with the following code:
class Outer3 {
private String privateStr = 'some string'
def startThread() {
new Thread(new Runnable() { (1)
void run() {
println "${privateStr}."
}
}).start() (2)
}
}
| 1 | comparing with the last example of previous section, the new Inner2() was replaced by new Runnable() along with all its implementation |
| 2 | the method start is invoked normally |
Thus, there was no need to define a new class to be used just once.
Abstract class
Abstract classes represent generic concepts, thus, they cannot be instantiated, being created to be subclassed. Their members include fields/properties and abstract or concrete methods. Abstract methods do not have implementation, and must be implemented by concrete subclasses.
abstract class Abstract { (1)
String name
abstract def abstractMethod() (2)
def concreteMethod() {
println 'concrete'
}
}
| 1 | abstract classes must be declared with abstract keyword |
| 2 | abstract methods must also be declared with abstract keyword |
Abstract classes are commonly compared to interfaces. There are at least two important differences of choosing one or another. First, while abstract classes may contain fields/properties and concrete methods, interfaces may contain only abstract methods (method signatures). Moreover, one class can implement several interfaces, whereas it can extend just one class, abstract or not.
Inheritance
Inheritance in Groovy resembles inheritance in Java. It provides a mechanism for a child class (or subclass) to reuse code or properties from a parent (or super class). Classes related through inheritance form an inheritance hierarchy. Common behavior and members are pushed up the hierarchy to reduce duplication. Specializations occur in child classes.
Different forms of inheritance are supported:
-
implementation inheritance where code (methods, fields or properties) from a superclass or from one or more traits is reused by a child class
-
contract inheritance where a class promises to provide particular abstract methods defined in a superclass, or defined in one or more traits or interfaces.
Superclasses
Parent classes share visible fields, properties or methods with child classes.
A child class may have at most one parent class.
The extends keyword is used immediately prior to giving the superclass type.
Interfaces
An interface defines a contract that a class needs to conform to. Typically, an interface defines zero or more abstract method definitions, but does not define the method’s implementation.
Here is a Greeter interface defining one greet method:
interface Greeter { (1)
void greet(String name) (2)
}
| 1 | an interface needs to be declared using the interface keyword |
| 2 | the abstract method signature for the greet method |
Such method signatures are public by default.
It is an error to use protected or package-private methods in interfaces:
interface Greeter {
protected void greet(String name) (1)
}
| 1 | Using protected is a compile-time error |
A class implements an interface if it defines the interface in its implements list or if any of its superclasses
does:
class SystemGreeter implements Greeter { (1)
void greet(String name) { (2)
println "Hello $name"
}
}
def greeter = new SystemGreeter()
assert greeter instanceof Greeter (3)
| 1 | The SystemGreeter declares the Greeter interface using the implements keyword |
| 2 | Then implements the required greet method |
| 3 | Any instance of SystemGreeter is also an instance of the Greeter interface |
An interface can extend another interface:
interface ExtendedGreeter extends Greeter { (1)
void sayBye(String name)
}
| 1 | the ExtendedGreeter interface extends the Greeter interface using the extends keyword |
It is worth noting that for a class to be an instance of an interface, it has to be explicit. For example, the following
class defines the greet method as it is declared in the Greeter interface, but does not declare Greeter in its
interfaces:
class DefaultGreeter {
void greet(String name) { println "Hello" }
}
greeter = new DefaultGreeter()
assert !(greeter instanceof Greeter)
In other words, Groovy does not define structural typing. It is however possible to make an instance of an object
implement an interface at runtime, using the as coercion operator:
greeter = new DefaultGreeter() (1)
coerced = greeter as Greeter (2)
assert coerced instanceof Greeter (3)
| 1 | create an instance of DefaultGreeter that does not implement the interface |
| 2 | coerce the instance into a Greeter at runtime |
| 3 | the coerced instance implements the Greeter interface |
You can see that there are two distinct objects: one is the source object, a DefaultGreeter instance, which does not
implement the interface. The other is an instance of Greeter that delegates to the coerced object.
| Groovy traits are close to interfaces, but support other important features described elsewhere in this manual. If you need more power than offered by interfaces, consider using traits. |
While interfaces typically contain abstract method definitions,
non-abstract methods are also possible.
Variants allowed are default, static, or private:
-
Default methods provide a mechanism to evolve API functionality. You can add new functionality to existing interfaces but maintain binary compatibility with code written for older versions of those interfaces. Shared functionality for default methods may be placed in private methods.
-
Static methods provide a mechanism to associate methods directly an interface class without it impacting the OO contracts or being overridden. You could use them for writing factory or utility methods without creating an additional utility class.
1.4.3. Class members
Constructors
Constructors are special methods used to initialize an object with a specific state. As with normal methods, it is possible for a class to declare more than one constructor, so long as each constructor has a unique type signature. If an object doesn’t require any parameters during construction, it may use a no-arg constructor. If no constructors are supplied, an empty no-arg constructor will be provided by the Groovy compiler.
Groovy supports two invocation styles:
-
positional parameters are used in a similar to how you would use Java constructors
-
named parameters allow you to specify parameter names when invoking the constructor.
Positional parameters
To create an object by using positional parameters, the respective class needs to declare one or more constructors. In the case of multiple constructors, each must have a unique type signature. The constructors can also be added to the class using the groovy.transform.TupleConstructor annotation.
Typically, once at least one constructor is declared, the class can only be instantiated by having one of its
constructors called. It is worth noting that, in this case, you can’t normally create the class with named parameters.
Groovy does support named parameters so long as the class contains a no-arg constructor or provides a constructor which
takes a Map argument as the first (and potentially only) argument - see the next section for details.
There are three forms of using a declared constructor. The first one is the normal Java way, with the new keyword.
The others rely on coercion of lists into the desired types. In this case, it is possible to coerce with the as
keyword and by statically typing the variable.
class PersonConstructor {
String name
Integer age
PersonConstructor(name, age) { (1)
this.name = name
this.age = age
}
}
def person1 = new PersonConstructor('Marie', 1) (2)
def person2 = ['Marie', 2] as PersonConstructor (3)
PersonConstructor person3 = ['Marie', 3] (4)
| 1 | Constructor declaration |
| 2 | Constructor invocation, classic Java way |
| 3 | Constructor usage, using coercion with as keyword |
| 4 | Constructor usage, using coercion in assignment |
Named parameters
If no (or a no-arg) constructor is declared, it is possible to create objects by passing parameters in the form of a
map (property/value pairs). This can be in handy in cases where one wants to allow several combinations of parameters.
Otherwise, by using traditional positional parameters it would be necessary to declare all possible constructors.
Having a constructor where the first (and perhaps only) argument is a Map argument is also supported - such a
constructor may also be added using the groovy.transform.MapConstructor annotation.
class PersonWOConstructor { (1)
String name
Integer age
}
def person4 = new PersonWOConstructor() (2)
def person5 = new PersonWOConstructor(name: 'Marie') (3)
def person6 = new PersonWOConstructor(age: 1) (4)
def person7 = new PersonWOConstructor(name: 'Marie', age: 2) (5)
| 1 | No constructor declared |
| 2 | No parameters given in the instantiation |
| 3 | name parameter given in the instantiation |
| 4 | age parameter given in the instantiation |
| 5 | name and age parameters given in the instantiation |
It is important to highlight, however, that this approach gives more power to the constructor caller, while imposing an increased responsibility on the caller to get the names and value types correct. Thus, if greater control is desired, declaring constructors using positional parameters might be preferred.
Notes:
-
While the example above supplied no constructor, you can also supply a no-arg constructor or a constructor where the first argument is a
Map, most typically it’s the only argument. -
When no (or a no-arg) constructor is declared, Groovy replaces the named constructor call by a call to the no-arg constructor followed by calls to the setter for each supplied named property.
-
When the first argument is a Map, Groovy combines all named parameters into a Map (regardless of ordering) and supplies the map as the first parameter. This can be a good approach if your properties are declared as
final(since they will be set in the constructor rather than after the fact with setters). -
You can support both named and positional construction by supply both positional constructors as well as a no-arg or Map constructor.
-
You can support hybrid construction by having a constructor where the first argument is a Map but there are also additional positional parameters. Use this style with caution.
Methods
Groovy methods are quite similar to other languages. Some peculiarities will be shown in the next subsections.
Method definition
A method is defined with a return type or with the def keyword, to make the return type untyped. A method can also receive any number of arguments, which may not have their types explicitly declared. Java modifiers can be used normally, and if no visibility modifier is provided, the method is public.
Methods in Groovy always return some value. If no return statement is provided, the value evaluated in the last line executed will be returned. For instance, note that none of the following methods uses the return keyword.
def someMethod() { 'method called' } (1)
String anotherMethod() { 'another method called' } (2)
def thirdMethod(param1) { "$param1 passed" } (3)
static String fourthMethod(String param1) { "$param1 passed" } (4)
| 1 | Method with no return type declared and no parameter |
| 2 | Method with explicit return type and no parameter |
| 3 | Method with a parameter with no type defined |
| 4 | Static method with a String parameter |
Named parameters
Like constructors, normal methods can also be called with named parameters.
To support this notation, a convention is used where the first argument to the method is a Map.
In the method body, the parameter values can be accessed as in normal maps (map.key).
If the method has just a single Map argument, all supplied parameters must be named.
def foo(Map args) { "${args.name}: ${args.age}" }
foo(name: 'Marie', age: 1)
Named parameters can be mixed with positional parameters.
The same convention applies, in this case, in addition to the Map argument as the first argument,
the method in question will have additional positional arguments as needed.
Supplied positional parameters when calling the method must be in order.
The named parameters can be in any position. They are grouped into the map and supplied as
the first parameter automatically.
def foo(Map args, Integer number) { "${args.name}: ${args.age}, and the number is ${number}" }
foo(name: 'Marie', age: 1, 23) (1)
foo(23, name: 'Marie', age: 1) (2)
| 1 | Method call with additional number argument of Integer type |
| 2 | Method call with changed order of arguments |
If we don’t have the Map as the first argument, then a Map must be supplied for that argument instead of named parameters.
Failure to do so will lead to groovy.lang.MissingMethodException:
def foo(Integer number, Map args) { "${args.name}: ${args.age}, and the number is ${number}" }
foo(name: 'Marie', age: 1, 23) (1)
| 1 | Method call throws groovy.lang.MissingMethodException: No signature of method: foo() is applicable for argument types: (LinkedHashMap, Integer) values: [[name:Marie, age:1], 23], because the named argument Map parameter is not defined as the first argument |
Above exception can be avoided if we replace named arguments with an explicit Map argument:
def foo(Integer number, Map args) { "${args.name}: ${args.age}, and the number is ${number}" }
foo(23, [name: 'Marie', age: 1]) (1)
| 1 | Explicit Map argument in place of named arguments makes invocation valid |
| Although Groovy allows you to mix named and positional parameters, it can lead to unnecessary confusion. Mix named and positional arguments with caution. |
Default arguments
Default arguments make parameters optional. If the argument is not supplied, the method assumes a default value.
def foo(String par1, Integer par2 = 1) { [name: par1, age: par2] }
assert foo('Marie').age == 1
Parameters are dropped from the right, however mandatory parameters are never dropped.
def baz(a = 'a', int b, c = 'c', boolean d, e = 'e') { "$a $b $c $d $e" }
assert baz(42, true) == 'a 42 c true e'
assert baz('A', 42, true) == 'A 42 c true e'
assert baz('A', 42, 'C', true) == 'A 42 C true e'
assert baz('A', 42, 'C', true, 'E') == 'A 42 C true E'
The same rule applies to constructors as well as methods.
If using @TupleConstructor, additional configuration options apply.
Varargs
Groovy supports methods with a variable number of arguments. They are defined like this: def foo(p1, …, pn, T… args).
Here foo supports n arguments by default, but also an unspecified number of further arguments exceeding n.
def foo(Object... args) { args.length }
assert foo() == 0
assert foo(1) == 1
assert foo(1, 2) == 2
This example defines a method foo, that can take any number of arguments, including no arguments at all.
args.length will return the number of arguments given. Groovy allows T[] as an alternative notation to T….
That means any method with an array as last parameter is seen by Groovy as a method that can take a variable number of arguments.
def foo(Object[] args) { args.length }
assert foo() == 0
assert foo(1) == 1
assert foo(1, 2) == 2
If a method with varargs is called with null as the vararg parameter, then the argument will be null and not an array of length one with null as the only element.
def foo(Object... args) { args }
assert foo(null) == null
If a varargs method is called with an array as an argument, then the argument will be that array instead of an array of length one containing the given array as the only element.
def foo(Object... args) { args }
Integer[] ints = [1, 2]
assert foo(ints) == [1, 2]
Another important point are varargs in combination with method overloading. In case of method overloading Groovy will select the most specific method.
For example if a method foo takes a varargs argument of type T and another method foo also takes one argument of type T, the second method is preferred.
def foo(Object... args) { 1 }
def foo(Object x) { 2 }
assert foo() == 1
assert foo(1) == 2
assert foo(1, 2) == 1
Method selection algorithm
Dynamic Groovy supports multiple dispatch (aka multimethods). When calling a method, the actual method invoked is determined dynamically based on the run-time type of methods arguments. First the method name and number of arguments will be considered (including allowance for varargs), and then the type of each argument. Consider the following method definitions:
def method(Object o1, Object o2) { 'o/o' }
def method(Integer i, String s) { 'i/s' }
def method(String s, Integer i) { 's/i' }
Perhaps as expected, calling method with String and Integer parameters,
invokes our third method definition.
assert method('foo', 42) == 's/i'
Of more interest here is when the types are not known at compile time.
Perhaps the arguments are declared to be of type Object (a list of such objects in our case).
Java would determine that the method(Object, Object) variant would be selected in all
cases (unless casts were used) but as can be seen in the following example, Groovy uses the runtime type
and will invoke each of our methods once (and normally, no casting is needed):
List<List<Object>> pairs = [['foo', 1], [2, 'bar'], [3, 4]]
assert pairs.collect { a, b -> method(a, b) } == ['s/i', 'i/s', 'o/o']
For each of the first two of our three method invocations an exact match of argument types was found.
For the third invocation, an exact match of method(Integer, Integer) wasn’t found but method(Object, Object)
is still valid and will be selected.
Method selection then is about finding the closest fit from valid method candidates which have compatible
parameter types.
So, method(Object, Object) is also valid for the first two invocations but is not as close a match
as the variants where types exactly match.
To determine the closest fit, the runtime has a notion of the distance an actual argument
type is away from the declared parameter type and tries to minimise the total distance across all parameters.
The following table illustrates some factors which affect the distance calculation.
| Aspect | Example |
|---|---|
Directly implemented interfaces match more closely than ones from further up the inheritance hierarchy. |
Given these interface and method definitions:
The directly implemented interface will match:
|
An Object array is preferred over an Object. |
|
Non-vararg variants are favored over vararg variants. |
|
If two vararg variants are applicable, the one which uses the minimum number of vararg arguments is preferred. |
|
Interfaces are preferred over super classes. |
|
For a primitive argument type, a declared parameter type which is the same or slightly larger is preferred. |
|
In the case where two variants have exactly the same distance, this is deemed ambiguous and will cause a runtime exception:
def method(Date d, Object o) { 'd/o' }
def method(Object o, String s) { 'o/s' }
def ex = shouldFail {
println method(new Date(), 'baz')
}
assert ex.message.contains('Ambiguous method overloading')
Casting can be used to select the desired method:
assert method(new Date(), (Object)'baz') == 'd/o'
assert method((Object)new Date(), 'baz') == 'o/s'
Exception declaration
Groovy automatically allows you to treat checked exceptions like unchecked exceptions.
This means that you don’t need to declare any checked exceptions that a method may throw
as shown in the following example which can throw a FileNotFoundException if the file isn’t found:
def badRead() {
new File('doesNotExist.txt').text
}
shouldFail(FileNotFoundException) {
badRead()
}
Nor will you be required to surround the call to the badRead method in the previous example within a try/catch
block - though you are free to do so if you wish.
If you wish to declare any exceptions that your code might throw (checked or otherwise) you are free to do so. Adding exceptions won’t change how the code is used from any other Groovy code but can be seen as documentation for the human reader of your code. The exceptions will become part of the method declaration in the bytecode, so if your code might be called from Java, it might be useful to include them. Using an explicit checked exception declaration is illustrated in the following example:
def badRead() throws FileNotFoundException {
new File('doesNotExist.txt').text
}
shouldFail(FileNotFoundException) {
badRead()
}
Fields and Properties
Fields
A field is a member of a class, interface or trait which stores data. A field defined in a Groovy source file has:
-
a mandatory access modifier (
public,protected, orprivate) -
one or more optional modifiers (
static,final,synchronized) -
an optional type
-
a mandatory name
class Data {
private int id (1)
protected String description (2)
public static final boolean DEBUG = false (3)
}
| 1 | a private field named id, of type int |
| 2 | a protected field named description, of type String |
| 3 | a public static final field named DEBUG of type boolean |
A field may be initialized directly at declaration:
class Data {
private String id = IDGenerator.next() (1)
// ...
}
| 1 | the private field id is initialized with IDGenerator.next() |
It is possible to omit the type declaration of a field. This is however considered a bad practice and in general it is a good idea to use strong typing for fields:
class BadPractice {
private mapping (1)
}
class GoodPractice {
private Map<String,String> mapping (2)
}
| 1 | the field mapping doesn’t declare a type |
| 2 | the field mapping has a strong type |
The difference between the two is important if you want to use optional type checking later. It is also important as a way to document the class design. However, in some cases like scripting or if you want to rely on duck typing it may be useful to omit the type.
Properties
A property is an externally visible feature of a class. Rather than just using a public field to represent such features (which provides a more limited abstraction and would restrict refactoring possibilities), the typical approach in Java is to follow the conventions outlined in the JavaBeans Specification, i.e. represent the property using a combination of a private backing field and getters/setters. Groovy follows these same conventions but provides a simpler way to define the property. You can define a property with:
-
an absent access modifier (no
public,protectedorprivate) -
one or more optional modifiers (
static,final,synchronized) -
an optional type
-
a mandatory name
Groovy will then generate the getters/setters appropriately. For example:
class Person {
String name (1)
int age (2)
}
| 1 | creates a backing private String name field, a getName and a setName method |
| 2 | creates a backing private int age field, a getAge and a setAge method |
If a property is declared final, no setter is generated:
class Person {
final String name (1)
final int age (2)
Person(String name, int age) {
this.name = name (3)
this.age = age (4)
}
}
| 1 | defines a read-only property of type String |
| 2 | defines a read-only property of type int |
| 3 | assigns the name parameter to the name field |
| 4 | assigns the age parameter to the age field |
Properties are accessed by name and will call the getter or setter transparently, unless the code is in the class which defines the property:
class Person {
String name
void name(String name) {
this.name = "Wonder $name" (1)
}
String title() {
this.name (2)
}
}
def p = new Person()
p.name = 'Diana' (3)
assert p.name == 'Diana' (4)
p.name('Woman') (5)
assert p.title() == 'Wonder Woman' (6)
| 1 | this.name will directly access the field because the property is accessed from within the class that defines it |
| 2 | similarly a read access is done directly on the name field |
| 3 | write access to the property is done outside of the Person class so it will implicitly call setName |
| 4 | read access to the property is done outside of the Person class so it will implicitly call getName |
| 5 | this will call the name method on Person which performs a direct access to the field |
| 6 | this will call the title method on Person which performs a direct read access to the field |
It is worth noting that this behavior of accessing the backing field directly is done in order to prevent a stack overflow when using the property access syntax within a class that defines the property.
It is possible to list the properties of a class thanks to the meta properties field of an instance:
class Person {
String name
int age
}
def p = new Person()
assert p.properties.keySet().containsAll(['name','age'])
By convention, Groovy will recognize properties even if there is no backing field provided there are getters or setters that follow the Java Beans specification. For example:
class PseudoProperties {
// a pseudo property "name"
void setName(String name) {}
String getName() {}
// a pseudo read-only property "age"
int getAge() { 42 }
// a pseudo write-only property "groovy"
void setGroovy(boolean groovy) { }
}
def p = new PseudoProperties()
p.name = 'Foo' (1)
assert p.age == 42 (2)
p.groovy = true (3)
| 1 | writing p.name is allowed because there is a pseudo-property name |
| 2 | reading p.age is allowed because there is a pseudo-readonly property age |
| 3 | writing p.groovy is allowed because there is a pseudo-write-only property groovy |
This syntactic sugar is at the core of many DSLs written in Groovy.
It is generally recommended that the first two letters of a property name are
lowercase and for multi-word properties that camel case is used.
In those cases, generated getters and setters will have a name formed by capitalizing the
property name and adding a get or set prefix (or optionally "is" for a boolean getter).
So, getLength would be a getter for a length property and setFirstName a setter for a firstName property.
isEmpty might be the getter method name for a property named empty.
|
Property names starting with a capital letter would have getters/setters with just the prefix added.
So, the property |
The JavaBeans specification makes a special case for properties which typically might be acronyms.
If the first two letters of a property name are uppercase, no capitalization is performed
(or more importantly, no decapitalization is done if generating the property name from the accessor method name).
So, getURL would be the getter for a URL property.
|
Because of the special "acronym handling" property naming logic in the JavaBeans specification, the
conversion to and from a property name are non-symmetrical. This leads to some strange edge cases.
Groovy adopts a naming convention that avoids one ambiguity that might seem a little strange but
was popular at the time of Groovy’s design and has remained (so far) for historical reasons.
Groovy looks at the second letter of a property name. If that is a capital, the property is deemed to be
one of the acronym style properties and no capitalization is done, otherwise normal capitalization is done.
Although we never recommend it, it does allow you to have what might seem like "duplicate named" properties,
e.g. you can have |
We have already seen that properties are defined by omitting the visibility modifier.
In general, any other modifiers, e.g. transient would be copied across to the field.
Two special cases are worth noting:
-
final, which we saw earlier is for read-only properties, is copied onto the backing field but also causes no setter to be defined -
staticis copied onto the backing field but also causes the accessor methods to be static
If you wish to have a modifier like final also carried over to the accessor methods,
you can write your properties long hand or consider using a split property definition.
Annotations, including those associated with AST transforms, are copied on to the backing field for the property. This allows AST transforms which are applicable to fields to be applied to properties, e.g.:
class Animal {
int lowerCount = 0
@Lazy String name = { lower().toUpperCase() }()
String lower() { lowerCount++; 'sloth' }
}
def a = new Animal()
assert a.lowerCount == 0 (1)
assert a.name == 'SLOTH' (2)
assert a.lowerCount == 1 (3)
| 1 | Confirms no eager initialization |
| 2 | Normal property access |
| 3 | Confirms initialization upon property access |
Groovy’s property syntax is a convenient shorthand when your class design follows certain conventions which align with common JavaBean practice. If your class doesn’t exactly fit these conventions, you can certainly write the getter, setter and backing field long hand like you would in Java. However, Groovy does provide a split definition capability which still provides a shortened syntax while allowing slight adjustments to the conventions. For a split definition, you write a field and a property with the same name and type. Only one of the field or property may have an initial value.
For split properties, annotations on the field remain on the backing field for the property. Annotations on the property part of the definition are copied onto the getter and setter methods.
This mechanism allows a number of common variations that property users may wish
to use if the standard property definition doesn’t exactly fit their needs.
For example, if the backing field should be protected rather than private:
class HasPropertyWithProtectedField {
protected String name (1)
String name (2)
}
| 1 | Protected backing field for name property instead of normal private one |
| 2 | Declare name property |
Or, the same example but with a package-private backing field:
class HasPropertyWithPackagePrivateField {
String name (1)
@PackageScope String name (2)
}
| 1 | Declare name property |
| 2 | Package-private backing field for name property instead of normal private one |
As a final example, we may wish to apply method-related AST transforms, or in general, any annotation to the setters/getters, e.g. to have the accessors be synchronized:
class HasPropertyWithSynchronizedAccessorMethods {
private String name (1)
@Synchronized String name (2)
}
| 1 | Backing field for name property |
| 2 | Declare name property with annotation for setter/getter |
The automatic generation of accessor methods doesn’t occur if there
is an explicit definition of the getter or setter in the class.
This allows you to modify the normal behavior of such a getter or setter if needed.
Inherited accessor methods aren’t normally considered but if an inherited
accessor method is marked final, that will also cause no generation of an
additional accessor method to honor the final requirement of no subclassing of such methods.
1.4.4. Annotations
Annotation definition
An annotation is a kind of special interface dedicated at annotating elements of the code. An annotation is a type which
superinterface is the java.lang.annotation.Annotation interface. Annotations are declared in a very
similar way to interfaces, using the @interface keyword:
@interface SomeAnnotation {}
An annotation may define members in the form of methods without bodies and an optional default value. The possible member types are limited to:
-
primitive types
-
another java.lang.annotation.Annotation
-
or any array of the above
For example:
@interface SomeAnnotation {
String value() (1)
}
@interface SomeAnnotation {
String value() default 'something' (2)
}
@interface SomeAnnotation {
int step() (3)
}
@interface SomeAnnotation {
Class appliesTo() (4)
}
@interface SomeAnnotation {}
@interface SomeAnnotations {
SomeAnnotation[] value() (5)
}
enum DayOfWeek { mon, tue, wed, thu, fri, sat, sun }
@interface Scheduled {
DayOfWeek dayOfWeek() (6)
}
| 1 | an annotation defining a value member of type String |
| 2 | an annotation defining a value member of type String with a default value of something |
| 3 | an annotation defining a step member of type the primitive type int |
| 4 | an annotation defining a appliesTo member of type Class |
| 5 | an annotation defining a value member which type is an array of another annotation type |
| 6 | an annotation defining a dayOfWeek member which type is the enumeration type DayOfWeek |
Unlike in the Java language, in Groovy, an annotation can be used to alter the semantics of the language. It is especially true of AST transformations which will generate code based on annotations.
Annotation placement
An annotation can be applied on various elements of the code:
@SomeAnnotation (1)
void someMethod() {
// ...
}
@SomeAnnotation (2)
class SomeClass {}
@SomeAnnotation String someVar (3)
| 1 | @SomeAnnotation applies to the someMethod method |
| 2 | @SomeAnnotation applies to the SomeClass class |
| 3 | @SomeAnnotation applies to the someVar variable |
In order to limit the scope where an annotation can be applied, it is necessary to declare it on the annotation definition, using the java.lang.annotation.Target annotation. For example, here is how you would declare that an annotation can be applied to a class or a method:
import java.lang.annotation.ElementType
import java.lang.annotation.Target
@Target([ElementType.METHOD, ElementType.TYPE]) (1)
@interface SomeAnnotation {} (2)
| 1 | the @Target annotation is meant to annotate an annotation with a scope. |
| 2 | @SomeAnnotation will therefore only be allowed on TYPE or METHOD |
The list of possible targets is available in the java.lang.annotation.ElementType.
| Groovy does not support the java.lang.annotation.ElementType#TYPE_PARAMETER and java.lang.annotation.ElementType#TYPE_PARAMETER element types which were introduced in Java 8. |
Annotation member values
When an annotation is used, it is required to set at least all members that do not have a default value. For example:
@interface Page {
int statusCode()
}
@Page(statusCode=404)
void notFound() {
// ...
}
However it is possible to omit value= in the declaration of the value of an annotation if the member value is the
only one being set:
@interface Page {
String value()
int statusCode() default 200
}
@Page(value='/home') (1)
void home() {
// ...
}
@Page('/users') (2)
void userList() {
// ...
}
@Page(value='error',statusCode=404) (3)
void notFound() {
// ...
}
| 1 | we can omit the statusCode because it has a default value, but value needs to be set |
| 2 | since value is the only mandatory member without a default, we can omit value= |
| 3 | if both value and statusCode need to be set, it is required to use value= for the default value member |
Retention policy
The visibility of an annotation depends on its retention policy. The retention policy of an annotation is set using the java.lang.annotation.Retention annotation:
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
@Retention(RetentionPolicy.SOURCE) (1)
@interface SomeAnnotation {} (2)
| 1 | the @Retention annotation annotates the @SomeAnnotation annotation |
| 2 | so @SomeAnnotation will have a SOURCE retention |
The list of possible retention targets and description is available in the java.lang.annotation.RetentionPolicy enumeration. The choice usually depends on whether you want an annotation to be visible at compile time or runtime.
Closure annotation parameters
An interesting feature of annotations in Groovy is that you can use a closure as an annotation value. Therefore annotations may be used with a wide variety of expressions and still have IDE support. For example, imagine a framework where you want to execute some methods based on environmental constraints like the JDK version or the OS. One could write the following code:
class Tasks {
Set result = []
void alwaysExecuted() {
result << 1
}
@OnlyIf({ jdk>=6 })
void supportedOnlyInJDK6() {
result << 'JDK 6'
}
@OnlyIf({ jdk>=7 && windows })
void requiresJDK7AndWindows() {
result << 'JDK 7 Windows'
}
}
For the @OnlyIf annotation to accept a Closure as an argument, you only have to declare the value as a Class:
@Retention(RetentionPolicy.RUNTIME)
@interface OnlyIf {
Class value() (1)
}
To complete the example, let’s write a sample runner that would use that information:
class Runner {
static <T> T run(Class<T> taskClass) {
def tasks = taskClass.newInstance() (1)
def params = [jdk: 6, windows: false] (2)
tasks.class.declaredMethods.each { m -> (3)
if (Modifier.isPublic(m.modifiers) && m.parameterTypes.length == 0) { (4)
def onlyIf = m.getAnnotation(OnlyIf) (5)
if (onlyIf) {
Closure cl = onlyIf.value().newInstance(tasks,tasks) (6)
cl.delegate = params (7)
if (cl()) { (8)
m.invoke(tasks) (9)
}
} else {
m.invoke(tasks) (10)
}
}
}
tasks (11)
}
}
| 1 | create a new instance of the class passed as an argument (the task class) |
| 2 | emulate an environment which is JDK 6 and not Windows |
| 3 | iterate on all declared methods of the task class |
| 4 | if the method is public and takes no arguments |
| 5 | try to find the @OnlyIf annotation |
| 6 | if it is found get the value and create a new Closure out of it |
| 7 | set the delegate of the closure to our environment variable |
| 8 | call the closure, which is the annotation closure. It will return a boolean |
| 9 | if it is true, call the method |
| 10 | if the method is not annotated with @OnlyIf, execute the method anyway |
| 11 | after that, return the task object |
Then the runner can be used this way:
def tasks = Runner.run(Tasks)
assert tasks.result == [1, 'JDK 6'] as Set
Meta-annotations
Declaring meta-annotations
Meta-annotations, also known as annotation aliases are annotations that are replaced at compile time by other annotations (one meta-annotation is an alias for one or more annotations). Meta-annotations can be used to reduce the size of code involving multiple annotations.
Let’s start with a simple example. Imagine you have the @Service
and @Transactional annotations and that you want to annotate a class
with both:
@Service
@Transactional
class MyTransactionalService {}
Given the multiplication of annotations that you could add to the same class, a meta-annotation could help by reducing the two annotations with a single one having the very same semantics. For example, we might want to write this instead:
@TransactionalService (1)
class MyTransactionalService {}
| 1 | @TransactionalService is a meta-annotation |
A meta-annotation is declared as a regular annotation but annotated with @AnnotationCollector and the
list of annotations it is collecting. In our case, the @TransactionalService annotation can be written:
import groovy.transform.AnnotationCollector
@Service (1)
@Transactional (2)
@AnnotationCollector (3)
@interface TransactionalService {
}
| 1 | annotate the meta-annotation with @Service |
| 2 | annotate the meta-annotation with @Transactional |
| 3 | annotate the meta-annotation with @AnnotationCollector |
Behavior of meta-annotations
Groovy supports both precompiled and source form meta-annotations. This means that your meta-annotation may be precompiled, or you can have it in the same source tree as the one you are currently compiling.
INFO: Meta-annotations are a Groovy-only feature. There is no chance for you to annotate a Java class with a meta-annotation and hope it will do the same as in Groovy. Likewise, you cannot write a meta-annotation in Java: both the meta-annotation definition and usage have to be Groovy code. But you can happily collect Java annotations and Groovy annotations within your meta-annotation.
When the Groovy compiler encounters a class annotated with a
meta-annotation, it replaces it with the collected annotations. So,
in our previous example, it will
replace @TransactionalService with @Transactional and @Service:
def annotations = MyTransactionalService.annotations*.annotationType()
assert (Service in annotations)
assert (Transactional in annotations)
The conversion from a meta-annotation to the collected annotations is performed during the semantic analysis compilation phase.
In addition to replacing the alias with the collected annotations, a meta-annotation is capable of processing them, including arguments.
Meta-annotation parameters
Meta-annotations can collect annotations which have parameters. To illustrate this, we will imagine two annotations, each of them accepting one argument:
@Timeout(after=3600)
@Dangerous(type='explosive')
And suppose that you want to create a meta-annotation named @Explosive:
@Timeout(after=3600)
@Dangerous(type='explosive')
@AnnotationCollector
public @interface Explosive {}
By default, when the annotations are replaced, they will get the annotation parameter values as they were defined in the alias. More interesting, the meta-annotation supports overriding specific values:
@Explosive(after=0) (1)
class Bomb {}
| 1 | the after value provided as a parameter to @Explosive overrides the one defined in the @Timeout annotation |
If two annotations define the same parameter name, the default processor will copy the annotation value to all annotations that accept this parameter:
@Retention(RetentionPolicy.RUNTIME)
public @interface Foo {
String value() (1)
}
@Retention(RetentionPolicy.RUNTIME)
public @interface Bar {
String value() (2)
}
@Foo
@Bar
@AnnotationCollector
public @interface FooBar {} (3)
@Foo('a')
@Bar('b')
class Bob {} (4)
assert Bob.getAnnotation(Foo).value() == 'a' (5)
println Bob.getAnnotation(Bar).value() == 'b' (6)
@FooBar('a')
class Joe {} (7)
assert Joe.getAnnotation(Foo).value() == 'a' (8)
println Joe.getAnnotation(Bar).value() == 'a' (9)
| 1 | the @Foo annotation defines the value member of type String |
| 2 | the @Bar annotation also defines the value member of type String |
| 3 | the @FooBar meta-annotation aggregates @Foo and @Bar |
| 4 | class Bob is annotated with @Foo and @Bar |
| 5 | the value of the @Foo annotation on Bob is a |
| 6 | while the value of the @Bar annotation on Bob is b |
| 7 | class Joe is annotated with @FooBar |
| 8 | then the value of the @Foo annotation on Joe is a |
| 9 | and the value of the @Bar annotation on Joe is also a |
In the second case, the meta-annotation value was copied in
both @Foo and @Bar annotations.
It is a compile time error if the collected annotations define the same members
with incompatible types. For example if on the previous example @Foo defined a value of
type String but @Bar defined a value of type int.
|
It is however possible to customize the behavior of meta-annotations and describe how collected annotations are expanded. We’ll look at how to do that shortly but first there is an advanced processing option to cover.
Handling duplicate annotations in meta-annotations
The @AnnotationCollector annotation supports a mode parameter which can be used to
alter how the default processor handles annotation replacement in the presence of
duplicate annotations.
INFO: Custom processors (discussed next) may or may not support this parameter.
As an example, suppose you create a meta-annotation containing the @ToString annotation
and then place your meta-annotation on a class that already has an explicit @ToString
annotation. Should this be an error? Should both annotations be applied? Does one take
priority over the other? There is no correct answer. In some scenarios it might be
quite appropriate for any of these answers to be correct. So, rather than trying to
preempt one correct way to handle the duplicate annotation issue, Groovy lets you
write your own custom meta-annotation processors (covered next) and lets you write
whatever checking logic you like within AST transforms - which are a frequent target for
aggregating. Having said that, by simply setting the mode, a number of commonly
expected scenarios are handled automatically for you within any extra coding.
The behavior of the mode parameter is determined by the AnnotationCollectorMode
enum value chosen and is summarized in the following table.
Mode |
Description |
DUPLICATE |
Annotations from the annotation collection will always be inserted. After all transforms have been run, it will be an error if multiple annotations (excluding those with SOURCE retention) exist. |
PREFER_COLLECTOR |
Annotations from the collector will be added and any existing annotations with the same name will be removed. |