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 //
, till 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 ag reeting 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.
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
The following list represents all the keywords of the Groovy language:
as |
assert |
break |
case |
catch |
class |
const |
continue |
def |
default |
do |
else |
enum |
extends |
false |
finally |
for |
goto |
if |
implements |
import |
in |
instanceof |
interface |
new |
null |
package |
return |
super |
switch |
this |
throw |
throws |
true |
try |
while |
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."Simson-${firstname}" = "Homer Simson"
assert map.'Simson-Homer' == "Homer Simson"
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 surounded by single quotes:
def firstname = "Homer"
map."Simson-${firstname}" = "Homer Simson"
assert map.'Simson-Homer' == "Homer Simson"
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 single quotes:
'a single quoted string'
Triple single quoted strings are plain java.lang.String and don’t support interpolation.
|
Triple single quoted strings are multiline. You can span the content of the string across line boundaries without the need to split the string in several pieces, without contatenation 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 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 |
---|---|
\t |
tabulation |
\b |
backspace |
\n |
newline |
\r |
carriage return |
\f |
formfeed |
\\ |
backslash |
\' |
single quote (for single quoted and triple single quoted strings) |
\" |
double quote (for double quoted and triple double quoted strings) |
Unicode escape sequence
For characters that are not present on your keyboard, you can use unicode escape sequances: 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 ${}
or prefixed with $
for dotted expressions.
The expression value inside the placeholder is evaluated to its string representation when the GString is passed to a method taking a String as argument by calling toString()
on that expression.
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'
But 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 expressions are actually allowed in between the ${} placeholder. Statements are also allowed, but 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, but expressions that would contain parentheses like method calls, curly braces for closures, 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}()" .
|
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 '${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 closure 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 | Similarily 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 paramaters are allowed. |
Inteoperability 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 delimiters.
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 also be interpolated (ie. a GString):
def color = 'blue'
def interpolatedSlashy = /a ${color} car/
assert interpolatedSlashy == 'a blue car'
There are a few gotchas to be aware of.
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 '' == //
Dollar slashy string
Dollar slashy strings are multiline GStrings delimited with an opening $/
and and a closing /$
.
The escaping character is the dollar sign, and it can escape another dollar, or a forward slash.
But both dollar and forward slashes don’t need to be escaped, except to escape the dollar of a string subsequence that would start like a GString placeholder sequence, or if you need to escape a sequence that would start like a closing dollar slashy string delimiter.
Here’s an example:
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 dollar slashy string delimiter
/$
assert [
'Guillaume',
'April, 1st',
'$ dollar sign',
'$ escaped dollar sign',
'\\ backslash',
'/ forward slash',
'$/ escaped forward slash',
'/$ escaped dollar slashy string delimiter'
].each { dollarSlashy.contains(it) }
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.lang.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
In Java 6 and before, as well as in Groovy, numbers could be represented in decimal, octal and hexadecimal bases, and with Java 7 and Groovy 2, you can use a binary notation with the 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.lang.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 a 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 choses java.lang.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 bellow), either uppercase or lowercase.
Type | Suffix |
---|---|
BigInteger |
|
Long |
|
Integer |
|
BigDecimal |
|
Double |
|
Float |
|
Examples:
assert 42I == new Integer('42')
assert 42i == new Integer('42') // lowercase i more readable
assert 123L == new Long("123") // uppercase L more readable
assert 2147483648 == new Long('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 1.200065D == new Double('1.200065')
assert 1.234F == new Float('1.234')
assert 1.23E23D == new Double('1.23E23')
assert 0b1111L.class == Long // binary
assert 0xFFi.class == Integer // hexadecimal
assert 034G.class == BigInteger // octal
Math operations
Although operators are covered later on, 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
,short
andint
result inint
-
binary operations involving
long
withbyte
,char
,short
andint
result inlong
-
binary operations involving
BigInteger
and any other integral type result inBigInteger
-
binary operations between
float
,double
andBigDecimal
result indouble
-
binary operations between two
BigDecimal
result 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 |
double |
char |
int |
int |
int |
long |
BigInteger |
double |
double |
double |
|
short |
int |
int |
long |
BigInteger |
double |
double |
double |
||
int |
int |
long |
BigInteger |
double |
double |
double |
|||
long |
long |
BigInteger |
double |
double |
double |
||||
BigInteger |
BigInteger |
double |
double |
double |
|||||
float |
double |
double |
double |
||||||
double |
double |
double |
|||||||
BigDecimal |
BigDecimal |
Thanks Groovy’s operator overloading, the usual arithmetic operators work as well with BigInteger and BigDecimal ,
unlike in Java where you have to use explict 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
(ie. 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
,Long
orDouble
if the result value fits in that type -
if the exponent is positive or zero
-
if the base is a
BigDecimal
, then return aBigDecimal
result value -
if the base is a
BigInteger
, then return aBigInteger
result value -
if the base is an
Integer
, then return anInteger
if the result value fits in it, otherwise aBigInteger
-
if the base is a
Long
, then return aLong
if 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 (zeroth-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 multi-dimensional lists:
def multi = [[0, 1], [2, 3]] (1)
assert multi[1][0] == 2 (2)
1 | Define a list of 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 explicitely 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) (3)
def numArr = [1, 2, 3] as int[] (4)
assert numArr instanceof int[] (5)
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’s array initializer notation is not supported by Groovy, as the curly braces can be misinterpreted with the notation of Groovy closures. |
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 | Similarily, 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
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 hash 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 |
|
|
substraction |
|
|
multiplication |
|
|
division |
Use |
|
modulo |
|
|
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 |
Assignment arithmetic operators
From the binary arithmetic operators we have seen above, certain of them 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
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 or lower 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 |
These operators into action in simple number comparisons:
assert 1 + 2 == 3
assert 3 != 4
assert -2 < 3
assert 2 <= 2
assert 3 <= 4
assert 5 > 1
assert 5 >= -2
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 && true (1)
1 | Here, the assertion is true, because "not" has a higher precedence than "and", otherwise, the assertion would have failed |
The logical "and" has a higher priority than the logical "or".
assert false || true && true (1)
1 | Here, the assertion is true, because "and" has a higher precedence than "or", otherwise, the assertion would have failed |
Short-circuiting for ||
The logical "or" operator is supporting short-circuiting: if the left operand is true, it won’t evaluate the right operand. The right operand will be evaluated only if the left operand is false.
called = false
boolean somethingTrueOrFalse(boolean b) { (1)
called = true
return b
}
assert true || somethingTrueOrFalse(false)
assert !called (2)
assert false || somethingTrueOrFalse(true)
assert called (3)
1 | We create a function that returns its boolean argument, but it sets the called flag |
2 | In the first case, we confirm that the function is not called, as || short-circuits the evaluation of the right operand. |
3 | In the second case, the right operand is called, as indicated by the fact our flag is now true |
1.2.4. Bitwise operators
Groovy offers 4 bitwise operators:
-
&
: bitwise "and" -
|
: bitwise "or" -
^
: bitwise "xor" (exclusive "or") -
~
: bitwise negation
Bitwise operators can be applied on a byte
or an int
and return an 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 have the particularity of being 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
or null
. 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 or null |
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.
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 reference operator
The method reference operator (.&
) call 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 references. First of all, the type of such a method reference 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 a returns a String |
3 | we create a method reference on that function |
4 | we create the list of elements we want to collect the descriptors |
5 | the method reference can be used where a Closure was expected |
Method references 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 reference on doSomething , without specifying argument types |
4 | using the method reference with a String calls the String version of doSomething |
5 | using the method reference with an Integer calls the Integer version of doSomething |
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! |
Find operator
Alternatively to building a pattern, you can directly use the find operator =~
to build 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()) |
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
, while
, etc.).
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/) |
1.2.8. Other operators (WIP)
Spread operator
The 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 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 of of the elements is null |
2 | using the spread operator will not throw a NullPointerException |
3 | the receiver might also be null, in which case the return value is null |
The spread operator can be used on any class which implements the Iterable
interface:
class Component {
Long id
String name
}
class CompositeObject implements Iterable<Component> {
def components = [
new Component(id:1, name: 'Foo'),
new Component(id:2, name:'Bar')]
@Override
Iterator<Component> iterator() {
components.iterator()
}
}
def composite = new CompositeObject()
assert composite*.id == [1,2]
assert composite*.name == ['Foo','Bar']
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) instanceof List (4)
assert (0..5).size() == 6 (5)
1 | a simple range of integers, stored into a local variable |
2 | an IntRange , with inclusive bounds |
3 | an IntRange , with exclusive upper bound |
4 | a groovy.lang.Range implements the List interface |
5 | meaning that you can call the size method on it |
Ranges implementation is lightweight, meaning that only the lower and upper bounds are stored. You can create a range
from any Comparable
object. 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 short hand notation for getAt
or putAt
, depending on whether you find it on
the left hand side or the right hand side of an assignment:
def list = [0,1,2,3,4]
assert list[2] == 2 (1)
list[2] = 4 (2)
assert list[0..2] == [0,1,4] (3)
list[0..2] = [6,6,6] (4)
assert list == [6,6,6,3,4] (5)
1 | [2] can be used instead of getAt(2) |
2 | if on left hand side of an assignment, will call putAt |
3 | getAt also supports ranges |
4 | so does putAt |
5 | the list is mutated |
The subscript operator, in combination with a custom implementation of getAt
/putAt
is a convenient way for destructuring
objects:
class User {
Long id
String name
def getAt(int i) { (1)
switch (i) {
case 0: return id
case 1: return name
}
throw new IllegalArgumentException("No such element $i")
}
void putAt(int i, def value) { (2)
switch (i) {
case 0: id = value; return
case 1: name = value; return
}
throw new IllegalArgumentException("No such element $i")
}
}
def user = new User(id: 1, name: 'Alex') (3)
assert user[0] == 1 (4)
assert user[1] == 'Alex' (5)
user[1] = 'Bob' (6)
assert user.name == 'Bob' (7)
1 | the User class defines a custom getAt implementation |
2 | the User class defines a custom putAt implementation |
3 | create a sample user |
4 | using the subscript operator with index 0 allows retrieving the user id |
5 | using the subscript operator with index 1 allows retrieving the user name |
6 | we can use the subscript operator to write to a property thanks to the delegation to putAt |
7 | and check that it’s really the property name which was changed |
Membership operator
The membership operator (in
) is equivalent to calling the isCase
method. In the context of a List
, it is equivalent
to calling contains
, like in the following example:
def list = ['Grace','Rob','Emmy']
assert ('Emmy' in list) (1)
1 | equivalent to calling list.contains('Emmy') or list.isCase('Emmy') |
Identity operator
In Groovy, using ==
to test equality is different from using the same operator in Java. In Groovy, it is calling equals
.
If you want to compare reference equality, you should use is
like in the following example:
def list1 = ['Groovy 1.8','Groovy 2.0','Groovy 2.3'] (1)
def list2 = ['Groovy 1.8','Groovy 2.0','Groovy 2.3'] (2)
assert list1 == list2 (3)
assert !list1.is(list2) (4)
1 | Create a list of strings |
2 | Create another list of strings containing the same elements |
3 | using == , we test object equality |
4 | but using is , we can check that references are distinct |
Coercion operator
The coercion operator (as
) is a variant of casting. Coercion converts object from one type to another without them
being compatible for assignement. Let’s take an example:
Integer x = 123
String s = (String) x (1)
1 | Integer is not assignable to a String , so it will produce a ClassCastException at runtime |
This can be fixed by using coercion instead:
Integer x = 123
String s = x as String (1)
1 | Integer is not assignable to a String , but use of as will coerce it to a String |
When an object is coerced into another, unless the target type is the same as the source type, coercion will return a
new object. The rules of coercion differ depending on the source and target types, and coercion may fail if no conversion
rules are found. Custom conversion rules may be implemented thanks to the asType
method:
class Identifiable {
String name
}
class User {
Long id
String name
def asType(Class target) { (1)
if (target==Identifiable) {
return new Identifiable(name: name)
}
throw new ClassCastException("User cannot be coerced into $target")
}
}
def u = new User(name: 'Xavier') (2)
def p = u as Identifiable (3)
assert p instanceof Identifiable (4)
assert !(p instanceof User) (5)
1 | the User class defines a custom conversion rule from User to Identifiable |
2 | we create an instance of User |
3 | we coerce the User instance into an Identifiable |
4 | the target is an instance of Identifiable |
5 | the target is not an instance of User anymore |
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 (TBD)
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) |
|
a.leftShift(b) |
|
a.div(b) |
|
a.rightShift(b) |
|
a.mod(b) |
|
a.next() |
|
a.power(b) |
|
a.previous() |
|
a.or(b) |
|
a.positive() |
|
a.and(b) |
|
a.negative() |
|
a.xor(b) |
|
a.bitwiseNegative() |
1.3. Program structure (Anto Aravinth)
This chapter covers the program structure of the Groovy programming language.
1.3.1. Package name (TBD)
1.3.2. Imports (TBD)
Default imports (TBD)
Simple import (TBD)
Star import (TBD)
Static import (TBD)
Static star import (TBD)
Import aliasing (TBD)
1.3.3. Scripts versus classes
Package name plays exactly the same role as they used to play in Java. They allows 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, as you can see
//Defines a package named com.yoursite
package com.yoursite
To refer the class Foo
in the com.yoursite.com
package then you need to use com.yoursite.com.Foo
in your code else you can use import
statement as we can see now.
1.3.4. Imports
In order to refer any class you need a reference for it. Groovy follows Java’s notion of allowing import
statement to resolve the class references. Groovy provides several builder classes,
one such class is MarkupBuilder
. MarkupBuilder
is inside the package groovy.xml
, in order to use this class, you need to import
it as shown:
//imports the class MarkupBuilder
import groovy.xml.MarkupBuilder
//uses 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:
new Date()
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. There are six packages that groovy imports for you, they are:
import java.lang.*
import java.util.*
import java.io.*
import java.net.*
import groovy.lang.*
import groovy.util.*
import java.math.BigInteger
import java.math.BigDecimal
Simple import
Simple import is an import, 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 the simple import, which directly refer the class inside the package.
//imports the class MarkupBuilder
import groovy.xml.MarkupBuilder
//uses 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 *
, they are called as Star import. MarkupBuilder
is class which is in package groovy.xml
, another class called StreamingMarkupBuilder
is in the same package. Consider the case, where you need to use two classes, then you can do:
import groovy.xml.MarkupBuilder
import groovy.xml.StreamingMarkupBuilder
def markupBuilder = new MarkupBuilder( )
assert markupBuilder != null
assert new StreamingMarkupBuilder() != null
Thats a perfectly valid code. But with *
import, we can do the same like this:
import groovy.xml.*
def markupBuilder = new MarkupBuilder( )
assert markupBuilder != null
assert new StreamingMarkupBuilder() != null
where, does imports all the class under package
groovy.xml
. Using import can clutters your local namespace. But with groovy type aliasing, 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. This is similar to Java’s static import capability but works with Java 1.4 and above and is a little 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. If you have the same types, the imported class takes precedence. Here is a sample of its usage:
import static Boolean.FALSE
assert !FALSE //use directly, without Boolean prefix!
As you can see, now we can able to refer the static variable FALSE
in our code base cleanly.
Static imports along with as
keyword can make elegant solution. Consider you want to get the Calendar
instance, using getInstance()
method. Its a static method, so we can do a static import. But instead of calling getInstance()
everytime, we can do this which can increase code readability:
import static Calendar.getInstance as now
assert now().class == Calendar.getInstance().class
Thats clean and sweet!
Static star import
Its very similar to the star import that we have seen earlier. It will import all the static methods from the given class. For example, lets say we need to calculate sin and cos for our application.
The class java.lang.Math
has static methods named sin
,cos
which fits our need. With help of 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
,cos
directly, instead like Math.sin
.
Import aliasing
With type aliasing, we can refer to a fully qualified class name with the name of your choice. This can be done with as
keyword in Groovy.
Consider the class with the following library code:
package thirdpartylib
public class MultiplyTwo {
def static multiply(def value) {
return value * 3 //intentionally wrong.
}
}
and we have some existing code using this library:
def result = new MultiplyTwo().multiply(2)
Obviously, the result
variable contains the wrong result. And assume you have used this library through out your code-base. Groovy has an elegant way to fix this issue.
With the type aliasing in place, we can fix the bug using the below code:
import thirdpartylib.MultiplyTwo as OrigMultiplyTwo
class MultiplyTwo extends OrigMultiplyTwo {
def multiply(def value)
{
return value * 2 //corrected here
}
}
// nothing changes below here
def multiplylib = new MultiplyTwo()
//assert passes as well
assert 4 == new MultiplyTwo().multiply(2)
Thats it! Notice how Groovy allowed us to use as
keyword to solve the typical problem.
With Type aliasing we can able to solve the name collision problem ( which can occur with *
imports) elegantly.
1.3.5. Initializers (TBD)
Static initializers (TBD)
Instance initializers (TBD)
1.4. Object orientation
This chapter covers the object orientation of the Groovy programming language.
1.4.1. Types (TBD)
Primitive types (TBD)
Class (TBD)
Normal class (TBD)
Static class (TBD)
Inner class (TBD)
Abstract class (TBD)
Interface (TBD)
Annotation (TBD)
Closure annotation parameters (TBD)
Meta-annotations (TBD)
Annotation placement (TBD)
Constructors (TBD)
Named argument constructor (TBD)
Methods (TBD)
Method definition (TBD)
Named arguments (TBD)
Default arguments (TBD)
Varargs (TBD)
Method selection algorithm (TBD)
Exception declaration (TBD)
Fields and properties (TBD)
Fields (TBD)
Properties (TBD)
Inheritance (TBD)
Generics (TBD)
1.4.2. Traits
Traits are a a structural construct of the language which allow:
-
composition of behaviors
-
runtime implementation of interfaces
-
behavior overriding
-
compatibility with static type checking/compilation
They can be seen as interfaces carrying both default implementations and state. A trait is defined using the
trait
keyword:
trait FlyingAbility { (1)
String fly() { "I'm flying!" } (2)
}
1 | declaration of a trait |
2 | declaration of a method inside a trait |
Then it can be used like a normal interface using the implements
keyword:
class Bird implements FlyingAbility {} (1)
def b = new Bird() (2)
assert b.fly() == "I'm flying!" (3)
1 | Adds the trait FlyingAbility to the Bird class capabilities |
2 | instantiate a new Bird |
3 | the Bird class automatically gets the behavior of the FlyingAbility trait |
Traits allow a wide range of capabilities, from simple composition to testing, which are described throughfully in this section.
Methods
Public methods
Declaring a method in a trait can be done like any regular method in a class:
trait FlyingAbility { (1)
String fly() { "I'm flying!" } (2)
}
1 | declaration of a trait |
2 | declaration of a method inside a trait |
Abstract methods
In addition, traits may declare abstract methods too, which therefore need to be implemented in the class implementing the trait:
trait Greetable {
abstract String name() (1)
String greeting() { "Hello, ${name()}!" } (2)
}
1 | implementing class will have to declare the name method |
2 | can be mixed with a concrete method |
Then the trait can be used like this:
class Person implements Greetable { (1)
String name() { 'Bob' } (2)
}
def p = new Person()
assert p.greeting() == 'Hello, Bob!' (3)
1 | implement the trait Greetable |
2 | since name was abstract, it is required to implement it |
3 | then greeting can be called |
Private methods
Traits may also define private methods. Those methods will not appear in the trait contract interface:
trait Greeter {
private String greetingMessage() { (1)
'Hello from a private method!'
}
String greet() {
def m = greetingMessage() (2)
println m
m
}
}
class GreetingMachine implements Greeter {} (3)
def g = new GreetingMachine()
assert g.greet() == "Hello from a private method!" (4)
try {
assert g.greetingMessage() (5)
} catch (MissingMethodException e) {
println "greetingMessage is private in trait"
}
1 | define a private method greetingMessage in the trait |
2 | the public greet message calls greetingMessage by default |
3 | create a class implementing the trait |
4 | greet can be called |
5 | but not greetingMessage |
Traits only support public and private methods. Neither protected nor package private scopes are
supported.
|
The meaning of this
this
represents the implementing instance. Think of a trait as a superclass. This means that when you write:
trait Introspector {
def whoAmI() { this }
}
class Foo implements Introspector {}
def foo = new Foo()
then calling:
foo.whoAmI()
will return the same instance:
assert foo.whoAmI().is(foo)
Interfaces
Traits may implement interfaces, in which case the interfaces are declared using the implements
keyword:
interface Named { (1)
String name()
}
trait Greetable implements Named { (2)
String greeting() { "Hello, ${name()}!" }
}
class Person implements Greetable { (3)
String name() { 'Bob' } (4)
}
def p = new Person()
assert p.greeting() == 'Hello, Bob!' (5)
assert p instanceof Named (6)
assert p instanceof Greetable (7)
1 | declaration of a normal interface |
2 | add Named to the list of implemented interfaces |
3 | declare a class that implements the Greetable trait |
4 | implement the missing greet method |
5 | the greeting implementation comes from the trait |
6 | make sure Person implements the Named interface |
7 | make sure Person implements the Greetable trait |
Properties
A trait may define properties, like in the following example:
trait Named {
String name (1)
}
class Person implements Named {} (2)
def p = new Person(name: 'Bob') (3)
assert p.name == 'Bob' (4)
assert p.getName() == 'Bob' (5)
1 | declare a property name inside a trait |
2 | declare a class which implements the trait |
3 | the property is automatically made visible |
4 | it can be accessed using the regular property accessor |
5 | or using the regular getter syntax |
Fields
Private fields
Since traits allow the use of private methods, it can also be interesting to use private fields to store state. Traits will let you do that:
trait Counter {
private int count = 0 (1)
int count() { count += 1; count } (2)
}
class Foo implements Counter {} (3)
def f = new Foo()
assert f.count() == 1 (4)
This is a major difference with Java 8 virtual extension methods. While virtual extension methods do not carry state, traits can. Also interesting traits in Groovy are supported starting with Java 6, but their implementation do not rely on virtual extension methods. This means that even if a trait can be seen from a Java class as a regular interface, this interface will not have default methods, only abstract ones. |
Public fields
Public fields work the same way as private fields, but in order to avoid the diamond problem, field names are remapped in the implementing class:
trait Named {
public String name (1)
}
class Person implements Named {} (2)
def p = new Person() (3)
p.Named__name = 'Bob' (4)
1 | declare a public field inside the trait |
2 | declare a class implementing the trait |
3 | create an instance of that class |
4 | the public field is available, but renamed |
The name of the field depends on the fully qualified name of the trait. All dots (.
) in package are replaced with an underscore (_
), and the final name includes a double underscore.
So if the type of the field is String
, the name of the package is my.package
, the name of the trait is Foo
and the name of the field is bar
,
in the implementing class, the public field will appear as:
String my_package_Foo__bar
While traits support public fields, it is not recommanded to use them and considered as a bad practice. |
Composition of behaviors
Traits can be used to implement multiple inheritance in a controlled way, avoiding the diamond issue. For example, we can have the following traits:
trait FlyingAbility { (1)
String fly() { "I'm flying!" } (2)
}
trait SpeakingAbility {
String speak() { "I'm speaking!" }
}
And a class implementing both traits:
class Duck implements FlyingAbility, SpeakingAbility {} (1)
def d = new Duck() (2)
assert d.fly() == "I'm flying!" (3)
assert d.speak() == "I'm speaking!" (4)
1 | the Duck class implements both FlyingAbility and SpeakingAbility |
2 | creates a new instance of Duck |
3 | we can call the method fly from FlyingAbility |
4 | but also the method speak from SpeakingAbility |
Traits encourage the reuse of capabilities among objects, and the creation of new classes by the composition of existing behavior.
Overriding default methods
Traits provide default implementations for methods, but it is possible to override them in the implementing class. For example, we can slightly change the example above, by having a duck which quacks:
class Duck implements FlyingAbility, SpeakingAbility {
String quack() { "Quack!" } (1)
String speak() { quack() } (2)
}
def d = new Duck()
assert d.fly() == "I'm flying!" (3)
assert d.quack() == "Quack!" (4)
assert d.speak() == "Quack!" (5)
1 | define a method specific to Duck , named quack |
2 | override the default implementation of speak so that we use quack instead |
3 | the duck is still flying, from the default implementation |
4 | quack comes from the Duck class |
5 | speak no longer uses the default implementation from SpeakingAbility |
Extending traits
Simple inheritance
Traits may extend another trait, in which case you must use the extends
keyword:
trait Named {
String name (1)
}
trait Polite extends Named { (2)
String introduce() { "Hello, I am $name" } (3)
}
class Person implements Polite {}
def p = new Person(name: 'Alice') (4)
assert p.introduce() == 'Hello, I am Alice' (5)
1 | the Named trait defines a single name property |
2 | the Polite trait extends the Named trait |
3 | Polite adds a new method which has access to the name property of the super-trait |
4 | the name property is visible from the Person class implementing Polite |
5 | as is the introduce method |
Multiple inheritance
Alternatively, a trait may extend multiple traits. In that case, all super traits must be declared in the implements
clause:
trait WithId { (1)
Long id
}
trait WithName { (2)
String name
}
trait Identified implements WithId, WithName {} (3)
1 | WithId trait defines the id property |
2 | WithName trait defines the name property |
3 | Identified is a trait which inherits both WithId and WithName |
Duck typing and traits
Dynamic code
Traits can call any dynamic code, like a normal Groovy class. This means that you can, in the body of a method, call methods which are supposed to exist in an implementing class, without having to explicitly declare them in an interface. This means that traits are fully compatible with duck typing:
trait SpeakingDuck {
String speak() { quack() } (1)
}
class Duck implements SpeakingDuck {
String methodMissing(String name, args) {
"${name.capitalize()}!" (2)
}
}
def d = new Duck()
assert d.speak() == 'Quack!' (3)
1 | the SpeakingDuck expects the quack method to be defined |
2 | the Duck class does implement the method using methodMissing |
3 | calling the speak method triggers a call to quack which is handled by methodMissing |
Dynamic methods in a trait
It is also possible for a trait to implement MOP methods like methodMissing
or propertyMissing
, in which case implementing classes
will inherit the behavior from the trait, like in this example:
trait DynamicObject { (1)
private Map props = [:]
def methodMissing(String name, args) {
name.toUpperCase()
}
def propertyMissing(String prop) {
props['prop']
}
void setProperty(String prop, Object value) {
props['prop'] = value
}
}
class Dynamic implements DynamicObject {
String existingProperty = 'ok' (2)
String existingMethod() { 'ok' } (3)
}
def d = new Dynamic()
assert d.existingProperty == 'ok' (4)
assert d.foo == null (5)
d.foo = 'bar' (6)
assert d.foo == 'bar' (7)
assert d.existingMethod() == 'ok' (8)
assert d.someMethod() == 'SOMEMETHOD' (9)
1 | create a trait implementing several MOP methods |
2 | the Dynamic class defines a property |
3 | the Dynamic class defines a method |
4 | calling an existing property will call the method from Dynamic |
5 | calling an non-existing property will call the method from the trait |
6 | will call setProperty defined on the trait |
7 | will call getProperty defined on the trait |
8 | calling an existing method on Dynamic |
9 | but calling a non existing method thanks to the trait methodMissing |
Multiple inheritance conflicts
Default conflict resolution
It is possible for a class to implement multiple traits. If some trait defines a method with the same signature as a method in another trait, we have a conflict:
trait A {
String exec() { 'A' } (1)
}
trait B {
String exec() { 'B' } (2)
}
class C implements A,B {} (3)
1 | trait A defines a method named exec returning a String |
2 | trait B defines the very same method |
3 | class C implements both traits |
In this case, the default behavior is that methods from the last declared trait wins. Here, B
is declared after A
so the method from B
will be picked up:
def c = new C()
assert c.exec() == 'B'
User conflict resolution
In case this behavior is not the one you want, you can explicitly choose which method to call using the Trait.super.foo
syntax.
In the example above, we can force to choose the method from trait A, by writing this:
class C implements A,B {
String exec() { A.super.exec() } (1)
}
def c = new C()
assert c.exec() == 'A' (2)
1 | explicit call of exec from the trait A |
2 | calls the version from A instead of using the default resolution, which would be the one from B |
Runtime implementation of traits
Implementing a trait at runtime
Groovy also supports implementing traits dynamically at runtime. It allows you to "decorate" an existing object using a trait. As an example, let’s start with this trait and the following class:
trait Extra {
String extra() { "I'm an extra method" } (1)
}
class Something { (2)
String doSomething() { 'Something' } (3)
}
1 | the Extra trait defines an extra method |
2 | the Something class does not implement the Extra trait |
3 | Something only defines a method doSomething |
Then if we do:
def s = new Something()
s.extra()
the call to extra would fail because Something
is not implementing Extra
. It is possible to do it at runtime with
the following syntax:
def s = new Something() as Extra (1)
s.extra() (2)
s.doSomething() (3)
1 | use of the as keyword to coerce an object to a trait at runtime |
2 | then extra can be called on the object |
3 | and doSomething is still callable |
When coercing an object to a trait, the result of the operation is not the same instance. It is guaranteed that the coerced object will implement both the trait and the interfaces that the original object implements, but the result will not be an instance of the original class. |
Implementing multiple traits at once
Should you need to implement several traits at once, you can use the withTraits
method instead of the as
keyword:
trait A { void methodFromA() {} }
trait B { void methodFromB() {} }
class C {}
def c = new C()
c.methodFromA() (1)
c.methodFromB() (2)
def d = c.withTraits A, B (3)
d.methodFromA() (4)
d.methodFromB() (5)
1 | call to methodFromA will fail because C doesn’t implement A |
2 | call to methodFromB will fail because C doesn’t implement B |
3 | withTrait will wrap c into something which implements A and B |
4 | methodFromA will now pass because d implements A |
5 | methodFromB will now pass because d also implements B |
When coercing an object to multiple traits, the result of the operation is not the same instance. It is guaranteed that the coerced object will implement both the traits and the interfaces that the original object implements, but the result will not be an instance of the original class. |
Chaining behavior
Groovy supports the concept of stackable traits. The idea is to delegate from one trait to the other if the current trait is not capable of handling a message. To illustrate this, let’s imagine a message handler interface like this:
interface MessageHandler {
void on(String message, Map payload)
}
Then you can compose a message handler by applying small behaviors. For example, let’s define a default handler in the form of a trait:
trait DefaultHandler implements MessageHandler {
void on(String message, Map payload) {
println "Received $message with payload $payload"
}
}
Then any class can inherit the behavior of the default handler by implementing the trait:
class SimpleHandler implements DefaultHandler {}
Now what if you want to log all messages, in addition to the default handler? One option is to write this:
class SimpleHandlerWithLogging implements DefaultHandler {
void on(String message, Map payload) { (1)
println "Seeing $message with payload $payload" (2)
DefaultHandler.super.on(message, payload) (3)
}
}
1 | explicitly implement the on method |
2 | perform logging |
3 | continue by delegating to the DefaultHandler trait |
This works but this approach has drawbacks:
-
the logging logic is bound to a "concrete" handler
-
we have an explicit reference to
DefaultHandler
in theon
method, meaning that if we happen to change the trait that our class implements, code will be broken
As an alternative, we can write another trait which responsability is limited to logging:
trait LoggingHandler implements MessageHandler { (1)
void on(String message, Map payload) {
println "Seeing $message with payload $payload" (2)
super.on(message, payload) (3)
}
}
1 | the logging handler is itself a handler |
2 | prints the message it receives |
3 | then super makes it delegate the call to the next trait in the chain |
Then our class can be rewritten as this:
class HandlerWithLogger implements DefaultHandler, LoggingHandler {}
def loggingHandler = new HandlerWithLogger()
loggingHandler.on('test logging', [:])
which will print:
Seeing test logging with payload [:] Received test logging with payload [:]
As the priority rules imply that LoggerHandler
wins because it is declared last, then a call to on
will use
the implementation from LoggingHandler
. But the latter has a call to super
, which means the next trait in the
chain. Here, the next trait is DefaultHandler
so both will be called:
The interest of this approach becomes more evident if we add a third handler, which is responsible for handling messages
that start with say
:
trait SayHandler implements MessageHandler {
void on(String message, Map payload) {
if (message.startsWith("say")) { (1)
println "I say ${message - 'say'}!"
} else {
super.on(message, payload) (2)
}
}
}
1 | a handler specific precondition |
2 | if the precondition is not meant, pass the message to the next handler in the chain |
Then our final handler looks like this:
class Handler implements DefaultHandler, SayHandler, LoggingHandler {}
def h = new Handler()
h.on('foo', [:])
h.on('sayHello', [:])
Which means:
-
messages will first go through the logging handler
-
the logging handler calls
super
which will delegate to the next handler, which is theSayHandler
-
if the message starts with
say
, then the hanlder consumes the message -
if not, the
say
handler delegates to the next handler in the chain
This approach is very powerful because it allows you to write handlers that do not know each other and yet let you combine them in the order you want. For example, if we execute the code, it will print:
Seeing foo with payload [:] Received foo with payload [:] Seeing sayHello with payload [:] I say Hello!
but if we move the logging handler to be the second one in the chain, the output is different:
class AlternateHandler implements DefaultHandler, LoggingHandler, SayHandler {}
h = new AlternateHandler()
h.on('foo', [:])
h.on('sayHello', [:])
prints:
Seeing foo with payload [:] Received foo with payload [:] I say Hello!
The reason is that now, since the SayHandler
consumes the message without calling super
, the logging handler is
not called anymore.
Semantics of super inside a trait
If a class implements multiple traits and that a call to an unqualified super
is found, then:
-
if the class implements another trait, the call delegates to the next trait in the chain
-
if there isn’t any trait left in the chain,
super
refers to the super class of the implementing class (this)
For example, it is possible to decorate final classes thanks to this behavior:
trait Filtering { (1)
StringBuilder append(String str) { (2)
def subst = str.replace('o','') (3)
super.append(subst) (4)
}
String toString() { super.toString() } (5)
}
def sb = new StringBuilder().withTraits Filtering (6)
sb.append('Groovy')
assert sb.toString() == 'Grvy' (7)
1 | define a trait named Filtering , supposed to be applied on a StringBuilder at runtime |
2 | redefine the append method |
3 | remove all 'o’s from the string |
4 | then delegate to super |
5 | in case toString is called, delegate to super.toString |
6 | runtime implementation of the Filtering trait on a StringBuilder instance |
7 | the string which has been appended no longer contains the letter o |
In this example, when super.append
is encountered, there is no other trait implemented by the target object, so the
method which is called is the original append
method, that is to say the one from StringBuilder
. The same trick
is used for toString
, so that the string representation of the proxy object which is generated delegates to the
toString
of the StringBuilder
instance.
Advanced features
SAM type coercion
If a trait defines a single abstract method, it is candidate for SAM type coercion. For example, imagine the following trait:
trait Greeter {
String greet() { "Hello $name" } (1)
abstract String getName() (2)
}
1 | the greet method is not abstract and calls the abstract method getName |
2 | getName is an abstract method |
Since getName
is the single abstract method in the Greeter
trait, you can write:
Greeter greeter = { 'Alice' } (1)
1 | the closure "becomes" the implementation of the getName single abstract method |
or even:
void greet(Greeter g) { println g.greet() } (1)
greet { 'Alice' } (2)
1 | the greet method accepts the SAM type Greeter as parameter |
2 | we can call it directly with a closure |
Differences with Java 8 default methods
In Java 8, interfaces can have default implementations of methods. If a class implements an interface and does not provide an implementation for a default method, then the implementation from the interface is chosen. Traits behave the same but with a major difference: the implementation from the trait is always used if the class declares the trait in its interface list and that it doesn’t provide an implementation.
This feature can be used to compose behaviors in an very precise way, in case you want to override the behavior of an already implemented method.
To illustrate the concept, let’s start with this simple example:
import groovy.transform.CompileStatic
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
import org.codehaus.groovy.control.customizers.ImportCustomizer
class SomeTest extends GroovyTestCase {
def config
def shell
void setup() {
config = new CompilerConfiguration()
shell = new GroovyShell(config)
}
void testSomething() {
assert shell.evaluate('1+1') == 2
}
void otherTest() { /* ... */ }
}
In this example, we create a simple test case which uses two properties (config and shell) and uses those in
multiple test methods. Now imagine that you want to test the same, but with another distinct compiler configuration.
One option is to create a subclass of SomeTest
:
class AnotherTest extends SomeTest {
void setup() {
config = new CompilerConfiguration()
config.addCompilationCustomizers( ... )
shell = new GroovyShell(config)
}
}
It works, but what if you have actually multiple test classes, and that you want to test the new configuration for all those test classes? Then you would have to create a distinct subclass for each test class:
class YetAnotherTest extends SomeTest {
void setup() {
config = new CompilerConfiguration()
config.addCompilationCustomizers( ... )
shell = new GroovyShell(config)
}
}
Then what you see is that the setup
method of both tests is the same. The idea, then, is to create a trait:
trait MyTestSupport {
void setup() {
config = new CompilerConfiguration()
config.addCompilationCustomizers( new ASTTransformationCustomizer(CompileStatic) )
shell = new GroovyShell(config)
}
}
Then use it in the subclasses:
class AnotherTest extends SomeTest implements MyTestSupport {}
class YetAnotherTest extends SomeTest2 implements MyTestSupport {}
...
It would allow us to dramatically reduce the boilerplate code, and reduces the risk of forgetting to change the setup
code in case we decide to change it. Even if setup
is already implemented in the super class, since the test class declares
the trait in its interface list, the behavior will be borrowed from the trait implementation!
This feature is in particular useful when you don’t have access to the super class source code. It can be used to mock methods or force a particular implementation of a method in a subclass. It lets you refactor your code to keep the overriden logic in a single trait and inherit a new behavior just by implementing it. The alternative, of course, is to override the method in every place you would have used the new code.
It’s worth noting that if you use runtime traits, the methods from the trait are always preferred to those of the proxied object: |
class Person {
String name (1)
}
trait Bob {
String getName() { 'Bob' } (2)
}
def p = new Person(name: 'Alice')
assert p.name == 'Alice' (3)
def p2 = p as Bob (4)
assert p2.name == 'Bob' (5)
1 | the Person class defines a name property which results in a getName method |
2 | Bob is a trait which defines getName as returning Bob |
3 | the default object will return Alice |
4 | p2 coerces p into Bob at runtime |
5 | getName returns Bob because getName is taken from the trait |
Again, don’t forget that dynamic trait coercion returns a distinct object which only implements the original interfaces, as well as the traits. |
Differences with mixins
There are several conceptual differences with mixins, as they are available in Groovy. Note that we are talking about runtime mixins, not the @Mixin annotation which is deprecated in favour of traits.
First of all, methods defined in a trait are visible in bytecode:
-
internally, the trait is represented as an interface (without default methods) and several helper classes
-
this means that an object implementing a trait effectively implements an interface
-
those methods are visible from Java
-
they are compatible with type checking and static compilation
Methods added through a mixin are, on the contrary, only visible at runtime:
class A { String methodFromA() { 'A' } } (1)
class B { String methodFromB() { 'B' } } (2)
A.metaClass.mixin B (3)
def o = new A()
assert o.methodFromA() == 'A' (4)
assert o.methodFromB() == 'B' (5)
assert o instanceof A (6)
assert !(o instanceof B) (7)
1 | class A defines methodFromA |
2 | class B defines methodFromB |
3 | mixin B into A |
4 | we can call methodFromA |
5 | we can also call methodFromB |
6 | the object is an instance of A |
7 | but it’s not an instanceof B |
The last point is actually a very important and illustrates a place where mixins have an advantage over traits: the instances are not modified, so if you mixin some class into another, there isn’t a third class generated, and methods which respond to A will continue responding to A even if mixed in.
Static methods, properties and fields
The following instructions are subject to caution. Static member support is work in progress and still experimental. The information below is valid for 2.3.6 only. |
It is possible to define static methods in a trait, but it comes with numerous limitations:
-
traits with static methods cannot be compiled statically or type checked. All static methods/properties/field are accessed dynamically (it’s a limitation from the JVM).
-
the trait is interpreted as a template for the implementing class, which means that each implementing class will get its own static methods/properties/methods. So a static member declared on a trait doesn’t belong to the
Trait
, but to it’s implementing class.
Let’s start with a simple example:
trait TestHelper {
public static boolean CALLED = false (1)
static void init() { (2)
CALLED = true (3)
}
}
class Foo implements TestHelper {}
Foo.init() (4)
assert Foo.TestHelper__CALLED (5)
1 | the static field is declared in the trait |
2 | a static method is also declared in the trait |
3 | the static field is updated within the trait |
4 | a static method init is made available to the implementing class |
5 | the static field is remapped to avoid the diamond issue |
As usual, it is not recommanded to use public fields. Anyway, should you want this, you must understand that the following code would fail:
Foo.CALLED = true
because there is no static field CALLED defined on the trait itself. Likewise, if you have two distinct implementing classes, each one gets a distinct static field:
class Bar implements TestHelper {} (1)
class Baz implements TestHelper {} (2)
Bar.init() (3)
assert Bar.TestHelper__CALLED (4)
assert !Baz.TestHelper__CALLED (5)
1 | class Bar implements the trait |
2 | class Baz also implements the trait |
3 | init is only called on Bar |
4 | the static field CALLED on Bar is updated |
5 | but the static field CALLED on Baz is not, because it is distinct |
Inheritance of state gotchas
We have seen that traits are stateful. It is possible for a trait to define fields or properties, but when a class implements a trait, it gets those fields/properties on a per-trait basis. So consider the following example:
trait IntCouple {
int x = 1
int y = 2
int sum() { x+y }
}
The trait defines two properties, x
and y
, as well as a sum
method. Now let’s create a class which implements the trait:
class BaseElem implements IntCouple {
int f() { sum() }
}
def base = new BaseElem()
assert base.f() == 3
The result of calling f
is 3
, because f
delegates to sum
in the trait, which has state. But what if we write this instead?
class Elem implements IntCouple {
int x = 3 (1)
int y = 4 (2)
int f() { sum() } (3)
}
def elem = new Elem()
1 | Override property x |
2 | Override property y |
3 | Call sum from trait |
If you call elem.f()
, what is the expected output? Actually it is:
assert elem.f() == 3
The reason is that the sum
method accesses the fields of the trait. So it is using the x
and y
values defined
in the trait. If you want to use the values from the implementing class, then you need to derefencence fields by using
getters and setters, like in this last example:
trait IntCouple {
int x = 1
int y = 2
int sum() { getX()+getY() }
}
class Elem implements IntCouple {
int x = 3
int y = 4
int f() { sum() }
}
def elem = new Elem()
assert elem.f() == 7
Limitations
Compatibility with AST transformations
Traits are not officially compatible with AST transformations. Some of them, like @CompileStatic will be applied
on the trait itself (not on implementing classes), while others will apply on both the implementing class and the trait.
There is absolutely no guarantee that an AST transformation will run on a trait as it does on a regular class, so use it
at your own risk!
|
Prefix and postfix operations
Within traits, prefix and postfix operations are not allowed if they update a field of the trait:
trait Counting {
int x
void inc() {
x++ (1)
}
void dec() {
--x (2)
}
}
class Counter implements Counting {}
def c = new Counter()
c.inc()
1 | x is defined within the trait, postfix increment is not allowed |
2 | x is defined within the trait, prefix decrement is not allowed |
A workaround is to use the +=
operator instead.
:leveloffset: 0
1.4.3. Closures (TBD)
This chapter covers Groovy Closures.
Syntax (TBD)
Parameters (TBD)
Normal parameters (TBD)
Implicit parameter (TBD)
Default parameter (TBD)
Varargs (TBD)
Delegation strategy (TBD)
Closures in GStrings (TBD)
Functional programming (TBD)
Currying (TBD)
Memoization (TBD)
Composition (TBD)
Trampoline (TBD)
1.4.4. Semantics
This chapter covers the semantics of the Groovy programming language.
Statements
Variable definition
Variables can be defined using either their type (like String
) or by using the keyword def
:
String x
def o
def
is a replacement for a type name. In variable definitions it is used to indicate that you don’t care about the type. In variable definitions it is mandatory to either provide a type name explicitly or to use "def" in replacement. This is needed to the make variable definitions detectable for the Groovy parser.
You can think of def
as an alias of Object
and you will understand it in an instant.
Variable definition types can be refined by using generics, like in List<String> names .
To learn more about the generics support, please read the generics section.
|
Variable assignment
You can assign values to variables for later use. Try the following:
x = 1
println x
x = new java.util.Date()
println x
x = -3.1499392
println x
x = false
println x
x = "Hi"
println x
Groovy supports multiple assignment, i.e. where multiple variables can be assigned at once, e.g.:
def (a, b, c) = [10, 20, 'foo']
assert a == 10 && b == 20 && c == 'foo'
You can provide types as part of the declaration if you wish:
def (int i, String j) = [10, 'foo']
assert i == 10 && j == 'foo'
As well as used when declaring variables it also applies to existing variables:
def nums = [1, 3, 5]
def a, b, c
(a, b, c) = nums
assert a == 1 && b == 3 && c == 5
The syntax works for arrays as well as lists, as well as methods that return either of these:
def (_, month, year) = "18th June 2009".split()
assert "In $month of $year" == 'In June of 2009'
If the left hand side has too many variables, excess ones are filled with null’s:
def (a, b, c) = [1, 2]
assert a == 1 && b == 2 && c == null
If the right hand side has too many variables, the extra ones are ignored:
def (a, b) = [1, 2, 3]
assert a == 1 && b == 2
In the section describing the various Groovy operators,
the case of the subscript operator has been covered,
explaining how you can override the getAt()
/putAt()
method.
With this technique, we can combine multiple assignments and the subscript operator methods to implement object destructuring.
Consider the following immutable Coordinates
class, containing a pair of longitude and latitude doubles,
and notice our implementation of the getAt()
method:
@Immutable
class Coordinates {
double latitude
double longitude
double getAt(int idx) {
if (idx == 0) latitude
else if (idx == 1) longitude
else throw new Exception("Wrong coordinate index, use 0 or 1")
}
}
Now let’s instantiate this class and destructure its longitude and latitude:
def coordinates = new Coordinates(latitude: 43.23, longitude: 3.67) (1)
def (la, lo) = coordinates (2)
assert la == 43.23 (3)
assert lo == 3.67
1 | we create an instance of the Coordinates class |
2 | then, we use a multiple assignment to get the individual longitude and latitude values |
3 | and we can finally assert their values. |
Control structures (WIP)
Groovy supports the usual if - else syntax from Java
def x = false
def y = false
if ( !x ) {
x = true
}
assert x == true
if ( x ) {
x = false
} else {
y = true
}
assert x == y
Groovy also supports the normal Java "nested" if then else if syntax:
if ( ... ) {
...
} else if (...) {
...
} else {
...
}
The switch statement in Groovy is backwards compatible with Java code; so you can fall through cases sharing the same code for multiple matches.
One difference though is that the Groovy switch statement can handle any kind of switch value and different kinds of matching can be performed.
def x = 1.23
def result = ""
switch ( x ) {
case "foo":
result = "found foo"
// lets fall through
case "bar":
result += "bar"
case [4, 5, 6, 'inList']:
result = "list"
break
case 12..30:
result = "range"
break
case Integer:
result = "integer"
break
case Number:
result = "number"
break
case ~/fo*/: // toString() representation of x matches the pattern?
result = "foo regex"
break
case { it < 0 }: // or { x < 0 }
result = "negative"
break
default:
result = "default"
}
assert result == "number"
Switch supports the following kinds of comparisons:
-
Class case values matches if the switch value is an instance of the class
-
Regular expression case values match if the
toString()
representation of the switch value matches the regex -
Collection case values match if the switch value is contained in the collection. This also includes ranges (since they are Lists)
-
Closure case values match if the calling the closure returns a result which is true according to the Groovy truth
-
If none of the above are used then the case value matches if the case value equals the switch value
default must go at the end of the switch/case. While in Java the default can be placed anywhere in the switch/case, the default in Groovy is used more as an else than assigning a default case.
|
when using a closure case value, the default it parameter is actually the switch value (in our example, variable x )
|
Groovy supports the standard Java / C for loop:
String message = ''
for (int i = 0; i < 5; i++) {
message += 'Hi '
}
assert message == 'Hi Hi Hi Hi Hi '
The for loop in Groovy is much simpler and works with any kind of array, collection, Map, etc.
// iterate over a range
def x = 0
for ( i in 0..9 ) {
x += i
}
assert x == 45
// iterate over a list
x = 0
for ( i in [0, 1, 2, 3, 4] ) {
x += i
}
assert x == 10
// iterate over an array
def array = (0..4).toArray()
x = 0
for ( i in array ) {
x += i
}
assert x == 10
// iterate over a map
def map = ['abc':1, 'def':2, 'xyz':3]
x = 0
for ( e in map ) {
x += e.value
}
assert x == 6
// iterate over values in a map
x = 0
for ( v in map.values() ) {
x += v
}
assert x == 6
// iterate over the characters in a string
def text = "abc"
def list = []
for (c in text) {
list.add(c)
}
assert list == ["a", "b", "c"]
Groovy also supports the Java colon variation with colons: for (char c : text) {} ,
where the type of the variable is mandatory.
|
Groovy supports the usual while {…} loops like Java:
def x = 0
def y = 5
while ( y-- > 0 ) {
x++
}
assert x == 5
Exception handling is the same as Java.
You can specify a complete try-catch-finally
, a try-catch
, or a try-finally
set of blocks.
Braces are required around each block’s body. |
try {
'moo'.toLong() // this will generate an exception
assert false // asserting that this point should never be reached
} catch ( e ) {
assert e in NumberFormatException
}
We can put code within a finally clause following a matching try clause, so that regardless of whether the code in the try clause throws an exception, the code in the finally clause will always execute:
def z
try {
def i = 7, j = 0
try {
def k = i / j
assert false //never reached due to Exception in previous line
} finally {
z = 'reached here' //always executed even if Exception thrown
}
} catch ( e ) {
assert e in ArithmeticException
assert z == 'reached here'
}
With the multi catch block (since Groovy 2.0), we’re able to define several exceptions to be catch and treated by the same catch block:
try {
/* ... */
} catch ( IOException | NullPointerException e ) {
/* one block to handle 2 exceptions */
}
Power assertion (TBD)
Labeled statements (TBD)
Expressions (TBD)
GPath expressions (TBD)
Promotion and coercion (TBD)
Number promotion (TBD)
Closure to type coercion
A SAM type is a type which defines a single abstract method. This includes:
interface Predicate<T> {
boolean accept(T obj)
}
abstract class Greeter {
abstract String getName()
void greet() {
println "Hello, $name"
}
}
Any closure can be converted into a SAM type using the as
operator:
Predicate filter = { it.contains 'G' } as Predicate
assert filter.accept('Groovy') == true
Greeter greeter = { 'Groovy' } as Greeter
greeter.greet()
However, the as Type
expression is optional since Groovy 2.2.0. You can omit it and simply write:
Predicate filter = { it.contains 'G' }
assert filter.accept('Groovy') == true
Greeter greeter = { 'Groovy' }
greeter.greet()
which means you are also allowed to use method pointers, as shown in the following example:
boolean doFilter(String s) { s.contains('G') }
Predicate filter = this.&doFilter
assert filter.accept('Groovy') == true
Greeter greeter = GroovySystem.&getVersion
greeter.greet()
The second and probably more important use case for closure to SAM type coercion is calling a method which accepts a SAM type. Imagine the following method:
public <T> List<T> filter(List<T> source, Predicate<T> predicate) {
source.findAll { predicate.accept(it) }
}
Then you can call it with a closure, without having to create an explicit implementation of the interface:
assert filter(['Java','Groovy'], { it.contains 'G'} as Predicate) == ['Groovy']
But since Groovy 2.2.0, you are also able to omit the explicit coercion and call the method as if it used a closure:
assert filter(['Java','Groovy']) { it.contains 'G'} == ['Groovy']
As you can see, this has the advantage of letting you use the closure syntax for method calls, that is to say put the closure outside of the parenthesis, improving the readability of your code.
In addition to SAM types, a closure can be coerced to any type and in particular interfaces. Let’s define the following interface:
interface FooBar {
int foo()
void bar()
}
You can coerce a closure into the interface using the as
keyword:
def impl = { println 'ok'; 123 } as FooBar
This produces a class for which all methods are implemented using the closure:
assert impl.foo() == 123
impl.bar()
But it is also possible to coerce a closure to any class. For example, we can replace the interface
that we defined
with class
without changing the assertions:
class FooBar {
int foo() { 1 }
void bar() { println 'bar' }
}
def impl = { println 'ok'; 123 } as FooBar
assert impl.foo() == 123
impl.bar()
Map to type coercion
Usually using a single closure to implement an interface or a class with multiple methods is not the way to go. As an
alternative, Groovy allows you to coerce a map into an interface or a class. In that case, keys of the map are
interpreted as method names, while the values are the method implementation. The following example illustrates the
coercion of a map into an Iterator
:
def map
map = [
i: 10,
hasNext: { map.i > 0 },
next: { map.i-- },
]
def iter = map as Iterator
Of course this is a rather contrived example, but illustrates the concept. You only need to implement those methods
that are actually called, but if a method is called that doesn’t exist in the map a MissingMethodException
or an
UnsupportedOperationException
is thrown, depending on the arguments passed to the call,
as in the following example:
interface X {
void f()
void g(int n)
void h(String s, int n)
}
x = [ f: {println "f called"} ] as X
x.f() // method exists
x.g() // MissingMethodException here
x.g(5) // UnsupportedOperationException here
The type of the exception depends on the call itself:
-
MissingMethodException
if the arguments of the call do not match those from the interface/class -
UnsupportedOperationException
if the arguments of the call match one of the overloaded methods of the interface/class
String to enum coercion
Groovy allows transparent String
(or GString
) to enum values coercion. Imagine you define the following enum:
enum State {
up,
down
}
then you can assign a string to the enum without having to use an explicit as
coercion:
State st = 'up'
assert st == State.up
It is also possible to use a GString
as the value:
def val = "up"
State st = "${val}"
assert st == State.up
However, this would throw a runtime error (IllegalArgumentException
):
State st = 'not an enum value'
Note that it is also possible to use implicit coercion in switch statements:
State switchState(State st) {
switch (st) {
case 'up':
return State.down // explicit constant
case 'down':
return 'up' // implicit coercion for return types
}
}
in particular, see how the case
use string constants. But if you call a method that uses an enum with a String
argument, you still have to use an explicit as
coercion:
assert switchState('up' as State) == State.down
assert switchState(State.down) == State.up
Custom type coercion
It is possible for a class to define custom coercion strategies by implementing the asType
method. Custom coercion
is invoked using the as
operator and is never implicit. As an example,
imagine you defined two classes, Polar
and Cartesian
, like in the following example:
class Polar {
double r
double phi
}
class Cartesian {
double x
double y
}
And that you want to convert from polar coordinates to cartesian coordinates. One way of doing this is to define
the asType
method in the Polar
class:
def asType(Class target) {
if (Cartesian==target) {
return new Cartesian(x: r*cos(phi), y: r*sin(phi))
}
}
which allows you to use the as
coercion operator:
def sigma = 1E-16
def polar = new Polar(r:1.0,phi:PI/2)
def cartesian = polar as Cartesian
assert abs(cartesian.x-sigma) < sigma
Putting it all together, the Polar
class looks like this:
class Polar {
double r
double phi
def asType(Class target) {
if (Cartesian==target) {
return new Cartesian(x: r*cos(phi), y: r*sin(phi))
}
}
}
but it is also possible to define asType
outside of the Polar
class, which can be practical if you want to define
custom coercion strategies for "closed" classes or classes for which you don’t own the source code, for example using
a metaclass:
Polar.metaClass.asType = { Class target ->
if (Cartesian==target) {
return new Cartesian(x: r*cos(phi), y: r*sin(phi))
}
}
Class literals vs variables and the as operator
Using the as
keyword is only possible if you have a static reference to a class, like in the following code:
interface Greeter {
void greet()
}
def greeter = { println 'Hello, Groovy!' } as Greeter // Greeter is known statically
greeter.greet()
But what if you get the class by reflection, for example by calling Class.forName
?
Class clazz = Class.forName('Greeter')
Trying to use the reference to the class with the as
keyword would fail:
greeter = { println 'Hello, Groovy!' } as clazz
// throws:
// unable to resolve class clazz
// @ line 9, column 40.
// greeter = { println 'Hello, Groovy!' } as clazz
It is failing because the as
keyword only works with class literals. Instead, you need to call the asType
method:
greeter = { println 'Hello, Groovy!' }.asType(clazz)
greeter.greet()
Optionality (TBD)
Optional parentheses (TBD)
Optional semicolons (TBD)
Optional return keyword (TBD)
Optional public keyword (TBD)
The Groovy Truth (TBD)
Customizing the truth with asBoolean() methods (TBD)
Typing (WIP)
Optional typing
Optional typing is the idea that a program can work even if you don’t put an explicit type on a variable. Being a dynamic language, Groovy naturally implements that feature, for example when you declare a variable:
String aString = 'foo' (1)
assert aString.toUpperCase() (2)
1 | foo is declared using an explicit type, String |
2 | we can call the toUpperCase method on a String |
Groovy will let you write this instead:
def aString = 'foo' (1)
assert aString.toUpperCase() (2)
1 | foo is declared using def |
2 | we can still call the toUpperCase method, because the type of aString is resolved at runtime |
So it doesn’t matter that you use an explicit type here. It is in particular interesting when you combine this feature with static type checking, because the type checker performs type inference.
Likewise, Groovy doesn’t make it mandatory to declare the types of a parameter in a method:
String concat(String a, String b) {
a+b
}
assert concat('foo','bar') == 'foobar'
can be rewritten using def
as both return type and parameter types, in order to take advantage of duck typing, as
illustrated in this example:
def concat(def a, def b) { (1)
a+b
}
assert concat('foo','bar') == 'foobar' (2)
assert concat(1,2) == 3 (3)
1 | both the return type and the parameter types use def |
2 | it makes it possible to use the method with String |
3 | but also with int`s since the `plus method is defined |
Using the def keyword here is recommanded to describe the intent of a method which is supposed to work on any
type, but technically, we could use Object instead and the result would be the same: def is, in Groovy, strictly
equivalent to using Object .
|
Eventually, the type can be removed altogether from both the return type and the descriptor. But if you want to remove it from the return type, you then need to add an explicit modifier for the method, so that the compiler can make a difference between a method declaration and a method call, like illustrated in this example:
private concat(a,b) { (1)
a+b
}
assert concat('foo','bar') == 'foobar' (2)
assert concat(1,2) == 3 (3)
1 | if we want to omit the return type, an explicit modifier has to be set. |
2 | it is still possible to use the method with String |
3 | and also with `int`s |
Omitting types is in general considered a bad practice in method parameters or method return types for public APIs.
While using def in a local variable is not really a problem because the visibility of the variable is limited to the
method itself, while set on a method parameter, def will be converted to Object in the method signature, making it
difficult for users to know which is the expected type of the arguments. This means that you should limit this to cases
where you are explicitly relying on duck typing.
|
Static type checking
By default, Groovy performs minimal type checking at compile time. Since it is primarily a dynamic language, most checks that a static compiler would normally do aren’t possible at compile time. A method added via runtime metaprogramming might alter a class or object’s runtime behavior. Let’s illustrate why in the following example:
class Person { (1)
String firstName
String lastName
}
def p = new Person(firstName: 'Raymond', lastName: 'Devos') (2)
assert p.formattedName == 'Raymond Devos' (3)
1 | the Person class only defines two properties, firstName and lastName |
2 | we can create an instance of Person |
3 | and call a method named formattedName |
It is quite common in dynamic languages for code such as the above example not to throw any error. How can this be?
In Java, this would typically fail at compile time. However, in Groovy, it will not fail at compile time, and if coded
correctly, will also not fail at runtime. In fact, to make this work at runtime, one possibility is to rely on
runtime metaprogramming. So just adding this line after the declaration of the Person
class is enough:
Person.metaClass.getFormattedName = { "$delegate.firstName $delegate.lastName" }
This means that in general, in Groovy, you can’t make any assumption about the type of an object beyond its declaration type, and even if you know it, you can’t determine at compile time what method will be called, or which property will be retrieved, and this is perfectly fine. This is how dynamic languages work, and it has a lot of interest.
However, if your program doesn’t rely on dynamic features and that you come from the static world (in particular, from
a Java mindset), not catching such "errors" at compile time can be surprising. As we have seen in the previous example,
the compiler cannot be sure this is an error. To make it aware that it is, you have to explicitly instruct the compiler
that you are switching to a type checked mode. This can be done by annotating a class or a method with @groovy.lang.TypeChecked
.
When type checking is activated, the compiler performs much more work:
-
type inference is activated, meaning that even if you use
def
on a local variable for example, the type checker will be able to infer the type of the variable from the assignments -
method calls are resolved at compile time, meaning that if a method is not declared on a class, the compiler will throw an error
-
in general, all the compile time errors that you are used to find in a static language will appear: method not found, property not found, incompatible types for method calls, number precision errors, …
In this section, we will describe the behavior of the type checker in various situations and explain the limits of using
@TypeChecked
on your code.
@TypeChecked
annotationThe groovy.lang.TypeChecked
annotation enabled type checking. It can be placed on a class:
@groovy.transform.TypeChecked
class Calculator {
int sum(int x, int y) { x+y }
}
Or on a method:
class Calculator {
@groovy.transform.TypeChecked
int sum(int x, int y) { x+y }
}
In the first case, all methods, properties, fields, inner classes, … of the annotated class will be type checked, whereas in the second case, only the method and potential closures or anonymous inner classes that it contains will be type checked.
The scope of type checking can be restricted. For example, if a class is type checked, you can instruct the type checker
to skip a method by annotating it with @TypeChecked(TypeCheckingMode.SKIP)
:
import groovy.transform.TypeChecked
import groovy.transform.TypeCheckingMode
@TypeChecked (1)
class GreetingService {
String greeting() { (2)
doGreet()
}
@TypeChecked(TypeCheckingMode.SKIP) (3)
private String doGreet() {
def b = new SentenceBuilder()
b.Hello.my.name.is.John (4)
b
}
}
def s = new GreetingService()
assert s.greeting() == 'Hello my name is John'
1 | the GreetingService class is marked as type checked |
2 | so the greeting method is automatically type checked |
3 | but doGreet is marked with SKIP |
4 | the type checker doesn’t complain about missing properties here |
In the previous example, SentenceBuilder
relies on dynamic code. There’s no real Hello
method or property, so the
type checker would normally complain and compilation would fail. Since the method that uses the builder is marked with
TypeCheckingMode.SKIP
, type checking is skipped for this method, so the code will compile, even if the rest of the
class is type checked.
The following sections describe the semantics of type checking in Groovy.
An object o
of type A
can be assigned to a variable of type T
if and only if:
-
T
equalsA
Date now = new Date()
-
or
T
is one ofString
,boolean
,Boolean
orClass
String s = new Date() // implicit call to toString Boolean boxed = 'some string' // Groovy truth boolean prim = 'some string' // Groovy truth Class clazz = 'java.lang.String' // class coercion
-
or
o
is null andT
is not a primitive typeString s = null // passes int i = null // fails
-
or
T
is an array andA
is an array and the component type ofA
is assignable to the component type ofB
int[] i = new int[4] // passes int[] i = new String[4] // fails
-
or
T
is an array andA
is a list and the component type ofA
is assignable to the component type ofB
int[] i = [1,2,3] // passes int[] i = [1,2, new Date()] // fails
-
or
T
is a superclass ofA
AbstractList list = new ArrayList() // passes LinkedList list = new ArrayList() // fails
-
or
T
is an interface implemented byA
List list = new ArrayList() // passes RandomAccess list = new LinkedList() // fails
-
or
T
orA
are a primitive type and their boxed types are assignableint i = 0 Integer bi = 1 int x = new Integer(123) double d = new Float(5f)
-
or
T
extendsgroovy.lang.Closure
andA
is a SAM-type (single abstract method type)Runnable r = { println 'Hello' } interface SAMType { int doSomething() } SAMType sam = { 123 } assert sam.doSomething() == 123 abstract class AbstractSAM { int calc() { 2* value() } abstract int value() } AbstractSAM c = { 123 } assert c.calc() == 246
-
or
T
andA
derive fromjava.lang.Number
and conform to the following table
T | A | Examples |
---|---|---|
Double |
Any but BigDecimal or BigInteger |
|
Float |
Any type but BigDecimal, BigInteger or Double |
|
Long |
Any type but BigDecimal, BigInteger, Double or Float |
|
Integer |
Any type but BigDecimal, BigInteger, Double, Float or Long |
|
Short |
Any type but BigDecimal, BigInteger, Double, Float, Long or Integer |
|
Byte |
Byte |
|
In addition to the assignment rules above, if an assignment is deemed invalid, in type checked mode, a list literal or a map literal A
can be assigned
to a variable of type T
if:
-
the assignment is a variable declaration and
A
is a list literal andT
has a constructor whose parameters match the types of the elements in the list literal -
the assignment is a variable declaration and
A
is a map literal andT
has a no-arg constructor and a property for each of the map keys
For example, instead of writing:
@groovy.transform.TupleConstructor
class Person {
String firstName
String lastName
}
Person classic = new Person('Ada','Lovelace')
You can use a "list constructor":
Person list = ['Ada','Lovelace']
or a "map constructor":
Person map = [firstName:'Ada', lastName:'Lovelace']
If you use a map constructor, additional checks are done on the keys of the map to check if a property of the same name is defined. For example, the following will fail at compile time:
@groovy.transform.TupleConstructor
class Person {
String firstName
String lastName
}
Person map = [firstName:'Ada', lastName:'Lovelace', age: 24] (1)
1 | The type checker will throw an error No such property: age for class: Person at compile time |
In type checked mode, methods are resolved at compile time. Resolution works by name and arguments. The return type is irrelevant to method selection. Types of arguments are matched against the types of the parameters following those rules:
An argument o
of type A
can be used for a parameter of type T
if and only if:
-
T
equalsA
int sum(int x, int y) { x+y } assert sum(3,4) == 7
-
or
T
is aString
andA
is aGString
String format(String str) { "Result: $str" } assert format("${3+4}") == "Result: 7"
-
or
o
is null andT
is not a primitive typeString format(int value) { "Result: $value" } assert format(7) == "Result: 7" format(null) // fails
-
or
T
is an array andA
is an array and the component type ofA
is assignable to the component type ofB
String format(String[] values) { "Result: ${values.join(' ')}" } assert format(['a','b'] as String[]) == "Result: a b" format([1,2] as int[]) // fails
-
or
T
is a superclass ofA
String format(AbstractList list) { list.join(',') } format(new ArrayList()) // passes String format(LinkedList list) { list.join(',') } format(new ArrayList()) // fails
-
or
T
is an interface implemented byA
String format(List list) { list.join(',') } format(new ArrayList()) // passes String format(RandomAccess list) { 'foo' } format(new LinkedList()) // fails
-
or
T
orA
are a primitive type and their boxed types are assignableint sum(int x, Integer y) { x+y } assert sum(3, new Integer(4)) == 7 assert sum(new Integer(3), 4) == 7 assert sum(new Integer(3), new Integer(4)) == 7 assert sum(new Integer(3), 4) == 7
-
or
T
extendsgroovy.lang.Closure
andA
is a SAM-type (single abstract method type)interface SAMType { int doSomething() } int twice(SAMType sam) { 2*sam.doSomething() } assert twice { 123 } == 246 abstract class AbstractSAM { int calc() { 2* value() } abstract int value() } int eightTimes(AbstractSAM sam) { 4*sam.calc() } assert eightTimes { 123 } == 984
-
or
T
andA
derive fromjava.lang.Number
and conform to the same rules as assignment of numbers
If a method with the appropriate name and arguments is not found at compile time, an error is thrown. The difference with "normal" Groovy is illustrated in the following example:
class MyService {
void doSomething() {
printLine 'Do something' (1)
}
}
1 | printLine is an error, but since we’re in a dynamic mode, the error is not caught at compile time |
The example above shows a class that Groovy will be able to compile. However, if you try to create an instance of MyService
and call the
doSomething
method, then it will fail at runtime, because printLine
doesn’t exist. Of course, we already showed how Groovy could make
this a perfectly valid call, for example by catching MethodMissingException
or implementing a custom meta-class, but if you know you’re
not in such a case, @TypeChecked
comes handy:
class MyService {
void doSomething() {
printLine 'Do something' (1)
}
}
1 | printLine is this time a compile-time error |
Just adding @TypeChecked
will trigger compile time method resolution. The type checker will try to find a method printLine
accepting
a String
on the MyService
class, but cannot find one. It will fail compilation with the following message:
Cannot find matching method MyService#printLine(java.lang.String)
It is important to understand the logic behind the type checker: it is a compile-time check, so by definition, the type checker
is not aware of any kind of runtime metaprogramming that you do. This means that code which is perfectly valid without @TypeChecked will
not compile anymore if you activate type checking. This is in particular true if you think of duck typing: |
class Duck {
void quack() { (1)
println 'Quack!'
}
}
class QuackingBird {
void quack() { (2)
println 'Quack!'
}
}
@groovy.transform.TypeChecked
void accept(quacker) {
quacker.quack() (3)
}
accept(new Duck()) (4)
1 | we define a Duck class which defines a quack method |
2 | we define another QuackingBird class which also defines a quack method |
3 | quacker is loosely typed, so since the method is @TypeChecked , we will obtain a compile-time error |
4 | even if in non type-checked Groovy, this would have passed |
There are possible workarounds, like introducing an interface, but basically, by activating type checking, you gain type safety but you loose some features of the language. Hopefully, Groovy introduces some features like flow typing to reduce the gap between type-checked and non type-checked Groovy.
When code is annotated with @TypeChecked
, the compiler performs type inference. It doesn’t simply rely on static types, but also uses various
techniques to infer the types of variables, return types, literals, … so that the code remains as clean as possible even if you activate the
type checker.
The simplest example is infering the type of a variable:
def message = 'Welcome to Groovy!' (1)
println message.toUpperCase() (2)
println message.upper() // compile time error (3)
1 | a variable is declared using the def keyword |
2 | calling toUpperCase is allowed by the type checker |
3 | calling upper will fail at compile time |
The reason the call to toUpperCase
works is because the type of message
was inferred as being a String
.
It is worth noting that although the compiler performs type inference on local variables, it does not perform any kind of type inference on fields, always falling back to the declared type of a field. To illustrate this, let’s take a look at this example:
class SomeClass {
def someUntypedField (1)
String someTypedField (2)
void someMethod() {
someUntypedField = '123' (3)
someUntypedField = someUntypedField.toUpperCase() // compile-time error (4)
}
void someSafeMethod() {
someTypedField = '123' (5)
someTypedField = someTypedField.toUpperCase() (6)
}
void someMethodUsingLocalVariable() {
def localVariable = '123' (7)
someUntypedField = localVariable.toUpperCase() (8)
}
}
1 | someUntypedField uses def as a declaration type |
2 | someTypedField uses String as a declaration type |
3 | we can assign anything to someUntypedField |
4 | yet calling toUpperCase fails at compile time because the field is not typed properly |
5 | we can assign a String to a field of type String |
6 | and this time toUpperCase is allowed |
7 | if we assign a String to a local variable |
8 | then calling toUpperCase is allowed on the local variable |
Why such a difference? The reason is thread safety. At compile time, we can’t make any guarantee about the type of a field. Any thread can access any field at any time and between the moment a field is assigned a variable of some type in a method and the time is is used the line after, another thread may have changed the contents of the field. This is not the case for local variables: we know if they "escape" or not, so we can make sure that the type of a variable is constant (or not) over time. Note that even if a field is final, the JVM makes no guarantee about it, so the type checker doesn’t behave differently if a field is final or not.
This is one of the reasons why we recommend to use typed fields. While using def for local variables is perfectly
fine thanks to type inference, this is not the case for fields, which also belong to the public API of a class, hence the
type is important.
|
Groovy provides a syntax for various type literals. There are three native collection literals in Groovy:
-
lists, using the
[]
literal -
maps, using the
[:]
literal -
ranges, using the
(..,..)
literal
The inferred type of a literal depends on the elements of the literal, as illustrated in the following table:
Literal | Inferred type |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
As you can see, with the noticeable exception of the IntRange
, the inferred type makes use of generics types to describe
the contents of a collection. In case the collection contains elements of different types, the type checker still performs
type inference of the components, but uses the notion of least upper bound.
In Groovy, the least upper bound of two types A
and B
is defined as a type which:
-
superclass corresponds to the common super class of
A
andB
-
interfaces correspond to the interfaces implemented by both
A
andB
-
if
A
orB
is a primitive type and thatA
isn’t equal toB
, the least upper bound ofA
andB
is the least upper bound of their wrapper types
If A
and B
only have one (1) interface in common and that their common superclass is Object
, then the LUB of both
is the common interface.
The least upper bound represents the minimal type to which both A
and B
can be assigned. So for example, if A
and B
are both String
, then the LUB (least upper bound) of both is also String
.
class Top {}
class Bottom1 extends Top {}
class Bottom2 extends Top {}
assert leastUpperBound(String, String) == String (1)
assert leastUpperBound(ArrayList, LinkedList) == AbstractList (2)
assert leastUpperBound(ArrayList, List) == List (3)
assert leastUpperBound(List, List) == List (4)
assert leastUpperBound(Bottom1, Bottom2) == Top (5)
assert leastUpperBound(List, Serializable) == Object (6)
1 | the LUB of String and String is String |
2 | the LUB of ArrayList and LinkedList is their common super type, AbstractList |
3 | the LUB of ArrayList and List is their only common interface, List |
4 | the LUB of two identical interfaces is the interface itself |
5 | the LUB of Bottom1 and Bottom2 is their superclass Top |
6 | the LUB of two types which have nothing in common is Object |
In those examples, the LUB is always representable as a normal, JVM supported, type. But Groovy internally represents the LUB as a type which can be more complex, and that you wouldn’t be able to use to define a variable for example. To illustrate this, let’s continue with this example:
interface Foo {}
class Top {}
class Bottom extends Top implements Serializable, Foo {}
class SerializableFooImpl implements Serializable, Foo {}
What is the least upper bound of Bottom
and SerializableFooImpl
? They don’t have a common super class (apart from Object
),
but they do share 2 interfaces (Serializable
and Foo
), so their least upper bound is a type which represents the union of
two interfaces (Serializable
and Foo
). This type cannot be defined in the source code, yet Groovy knows about it.
In the context of collection type inference (and generic type inference in general), this becomes handy, because the type of the components is inferred as the least upper bound. We can illustrate why this is important in the following example:
interface Greeter { void greet() } (1)
interface Salute { void salute() } (2)
class A implements Greeter, Salute { (3)
void greet() { println "Hello, I'm A!" }
void salute() { println "Bye from A!" }
}
class B implements Greeter, Salute { (4)
void greet() { println "Hello, I'm B!" }
void salute() { println "Bye from B!" }
void exit() { println 'No way!' } (5)
}
def list = [new A(), new B()] (6)
list.each {
it.greet() (7)
it.salute() (8)
it.exit() (9)
}
1 | the Greeter interface defines a single method, greet |
2 | the Salute interface defines a single method, salute |
3 | class A implements both Greeter and Salute but there’s no explicit interface extending both |
4 | same for B |
5 | but B defines an additional exit method |
6 | the type of list is inferred as "list of the LUB of A and B " |
7 | so it is possible to call greet which is defined on both A and B through the Greeter interface |
8 | and it is possible to call salut which is defined on both A and B through the Salut interface |
9 | yet calling exit is a compile time error because it doesn’t belong to the LUB of A and B (only defined in B ) |
The error message will look like:
[Static type checking] - Cannot find matching method Greeter or Salute#exit()
which indicates that the exit
method is neither defines on Greeter
nor Salute
, which are the two interfaces defined
in the least upper bound of A
and B
.
In normal, non type checked, Groovy, you can write things like:
class Greeter {
String greeting() { 'Hello' }
}
void doSomething(def o) {
if (o instanceof Greeter) { (1)
println o.greeting() (2)
}
}
doSomething(new Greeter())
1 | guard the method call with an instanceof check |
2 | make the call |
The method call works because of dynamic dispatch (the method is selected at runtime). The equivalent code in Java would
require to cast o
to a Greeter
before calling the greeting
method, because methods are selected at compile time:
if (o instanceof Greeter) {
System.out.println(((Greeter)o).greeting());
}
However, in Groovy, even if you add @TypeChecked
(and thus activate type checking) on the doSomething
method, the
cast is not necessary. The compiler embeds instanceof inference that makes the cast optional.
Flow typing is an important concept of Groovy in type checked mode and an extension of type inference. The idea is that the compiler is capable of infering the type of variables in the flow of the code, not just at initialization:
@groovy.transform.TypeChecked
void flowTyping() {
def o = 'foo' (1)
o = o.toUpperCase() (2)
o = 9d (3)
o = Math.sqrt(o) (4)
}
1 | first, o is declared using def and assigned a String |
2 | the compiler inferred that o is a String , so calling toUpperCase is allowed |
3 | o is reassigned with a double |
4 | calling Math.sqrt passes compilation because the compiler knows that at this point, o is a double |
So the type checker is aware of the fact that the concrete type of a variable is different over time. In particular, if you replace the last assignment with:
o = 9d
o = o.toUpperCase()
The type checker will now fail at compile time, because it knows that o
is a double
when toUpperCase
is called,
so it’s a type error.
It is important to understand that it is not the fact of declaring a variable with def
that triggers type inference.
Flow typing works for any variable of any type. Declaring a variable with an explicit type only constraints what you
can assign to a variable:
@groovy.transform.TypeChecked
void flowTypingWithExplicitType() {
List list = ['a','b','c'] (1)
list = list*.toUpperCase() (2)
list = 'foo' (3)
}
1 | list is declared as an unchecked List and assigned a list literal of `String`s |
2 | this line passes compilation because of flow typing: the type checker knows that list is at this point a List<String> |
3 | but you can’t assign a String to a List so this is a type checking error |
You can also note that even if the variable is declared without generics information, the type checker knows what is the component type. Therefore, such code would fail compilation:
@groovy.transform.TypeChecked
void flowTypingWithExplicitType() {
List list = ['a','b','c'] (1)
list.add(1) (2)
}
1 | list is inferred as List<String> |
2 | so adding an int to a List<String> is a compile-time error |
Fixing this requires adding an explicit generic type to the declaration:
@groovy.transform.TypeChecked
void flowTypingWithExplicitType() {
List<? extends Serializable> list = [] (1)
list.addAll(['a','b','c']) (2)
list.add(1) (3)
}
1 | list declared as List<? extends Serializable> and initialized with an empty list |
2 | elements added to the list conform to the declaration type of the list |
3 | so adding an int to a List<? extends Serializable> is allowed |
Flow typing has been introduced to reduce the difference in semantics between classic and static Groovy. In particular, consider the behavior of this code in Java:
public Integer compute(String str) {
return str.length();
}
public String compute(Object o) {
return "Nope";
}
// ...
Object string = "Some string"; (1)
Object result = compute(string); (2)
System.out.println(result); (3)
1 | o is declared as an Object and assigned a String |
2 | we call the compute method with o |
3 | and print the result |
In Java, this code will output 0
, because method selection is done at compile time and based on the declared types.
So even if o
is a String
at runtime, it is still the Object
version which is called, because o
has been declared
as an Object
. To be short, in Java, declared types are most important, be it variable types, parameter types or return
types.
In Groovy, we could write:
int compute(String string) { string.length() }
String compute(Object o) { "Nope" }
Object o = 'string'
def result = compute(o)
println result
But this time, it will return 6
, because the method which is chosen is chosen at runtime, based on the actual
argument types. So at runtime, o
is a String
so the String
variant is used. Note that this behavior has nothing
to do with type checking, it’s the way Groovy works in general: dynamic dispatch.
In type checked Groovy, we want to make sure the type checker selects the same method at compile time, that the runtime
would choose. It is not possible in general, due to the semantics of the language, but we can make things better with flow
typing. With flow typing, o
is inferred as a String
when the compute
method is called, so the version which takes
a String
and returns an int
is chosen. This means that we can infer the return type of the method to be an int
, and
not a String
. This is important for subsequent calls and type safety.
So in type checked Groovy, flow typing is a very important concept, which also implies that if @TypeChecked
is applied,
methods are selected based on the inferred types of the arguments, not on the declared types. This doesn’t ensure 100%
type safety, because the type checker may select a wrong method, but it ensures the closest semantics to dynamic Groovy.
A combination of flow typing and least upper bound inference is used to perform advanced type inference and ensure type safety in multiple situations. In particular, program control structures are likely to alter the inferred type of a variable:
class Top {
void methodFromTop() {}
}
class Bottom extends Top {
void methodFromBottom() {}
}
def o
if (someCondition) {
o = new Top() (1)
} else {
o = new Bottom() (2)
}
o.methodFromTop() (3)
o.methodFromBottom() // compilation error (4)
1 | if someCondition is true, o is assigned a Top |
2 | if someCondition is false, o is assigned a Bottom |
3 | calling methodFromTop is safe |
4 | but calling methodFromBottom is not, so it’s a compile time error |
When the type checker visits an if/else
control structure, it checks all variables which are assigned in if/else
branches
and computes the least upper bound of all assignments. This type is the type of the inferred variable
after the if/else
block, so in this example, o
is assigned a Top
in the if
branch and a Bottom
in the else
branch. The LUB of those is a Top
, so after the conditional branches, the compiler infers o
as being
a Top
. Calling methodFromTop
will therefore be allowed, but not methodFromBottom
.
The same reasoning exists with closures and in particular closure shared variables. A closure shared variable is a variable which is defined outside of a closure, but used inside a closure, as in this example:
def text = 'Hello, world!' (1)
def closure = {
println text (2)
}
1 | a variable named text is declared |
2 | text is used from inside a closure. It is a closure shared variable. |
Groovy allows developers to use those variables without requiring them to be final. This means that a closure shared variable can be reassigned inside a closure:
String result
doSomething { String it ->
result = "Result: $it"
}
result = result?.toUpperCase()
The problem is that a closure is an independent block of code that can be executed (or not) at any time. In particular,
doSomething
may be asynchronous, for example. This means that the body of a closure doesn’t belong to the main control
flow. For that reason, the type checker also computes, for each closure shared variable, the LUB of all
assignments of the variable, and will use that LUB
as the inferred type outside of the scope of the closure, like in
this example:
class Top {
void methodFromTop() {}
}
class Bottom extends Top {
void methodFromBottom() {}
}
def o = new Top() (1)
Thread.start {
o = new Bottom() (2)
}
o.methodFromTop() (3)
o.methodFromBottom() // compilation error (4)
1 | a closure-shared variable is first assigned a Top |
2 | inside the closure, it is assigned a Bottom |
3 | methodFromTop is allowed |
4 | methodFromBottom is a compilation error |
Here, it is clear that when methodFromBottom
is called, there’s no guarantee, at compile-time or runtime that the
type of o
will effectively be a Bottom
. There are chances that it will be, but we can’t make sure, because it’s
asynchronous. So the type checker will only allow calls on the least upper bound, which is here a Top
.
The type checker performs special inference on closures, resulting on additional checks on one side and improved fluency on the other side.
The first thing that the type checker is capable of doing is infering the return type of a closure. This is simply illustrated in the following example:
@groovy.transform.TypeChecked
int testClosureReturnTypeInference(String arg) {
def cl = { "Arg: $arg" } (1)
def val = cl() (2)
val.length() (3)
}
1 | a closure is defined, and it returns a string (more precisely a GString ) |
2 | we call the closure and assign the result to a variable |
3 | the type checker inferred that the closure would return a string, so calling length() is allowed |
As you can see, unlike a method which declares its return type explicitly, there’s no need to declare the return type of a closure: its type is inferred from the body of the closure.
In addition to the return type, it is possible for a closure to infer its parameter types from the context. There are two ways for the compiler to infer the parameter types:
-
through implicit SAM type coercion
-
through API metadata
To illustrate this, lets start with an example that will fail compilation due to the inability for the type checker to infer the parameter types:
class Person {
String name
int age
}
void inviteIf(Person p, Closure<Boolean> predicate) { (1)
if (predicate.call(p)) {
// send invite
// ...
}
}
@groovy.transform.TypeChecked
void failCompilation() {
Person p = new Person(name: 'Gerard', age: 55)
inviteIf(p) { (2)
it.age >= 18 // No such property: age (3)
}
}
1 | the inviteIf method accepts a Person and a Closure |
2 | we call it with a Person and a Closure |
3 | yet it is not statically known as being a Person and compilation fails |
In this example, the closure body contains it.age
. With dynamic, not type checked code, this would work, because the
type of it
would be a Person
at runtime. Unfortunately, at compile-time, there’s no way to know what is the type
of it
, just by reading the signature of inviteIf
.
To be short, the type checker doesn’t have enough contextual information on the inviteIf
method to determine statically
the type of it
. This means that the method call needs to be rewritten like this:
inviteIf(p) { Person it -> (1)
it.age >= 18
}
1 | the type of it needs to be declared explicitly |
By explicitly declaring the type of the it
variable, you can workaround the problem and make this code statically
checked.
For an API or framework designer, there are two ways to make this more elegant for users, so that they don’t have to declare an explicit type for the closure parameters. The first one, and easiest, is to replace the closure with a SAM type:
interface Predicate<On> { boolean apply(On e) } (1)
void inviteIf(Person p, Predicate<Person> predicate) { (2)
if (predicate.apply(p)) {
// send invite
// ...
}
}
@groovy.transform.TypeChecked
void passesCompilation() {
Person p = new Person(name: 'Gerard', age: 55)
inviteIf(p) { (3)
it.age >= 18 (4)
}
}
1 | declare a SAM interface with an apply method |
2 | inviteIf now uses a Predicate<Person> instead of a Closure<Boolean> |
3 | there’s no need to declare the type of the it variable anymore |
4 | it.age compiles properly, the type of it is inferred from the Predicate#apply method signature |
By using this technique, we leverage the automatic coercion of closures to SAM types feature of Groovy. The question whether you should use a SAM type or a Closure really depends on what you need to do. In a lot of cases, using a SAM interface is enough, especially if you consider functional interfaces as they are found in Java 8. However, closures provide features that are not accessible to functional interfaces. In particular, closures can have a delegate, and owner and can be manipulated as objects (for example, cloned, serialized, curried, …) before being called. They can also support multiple signatures (polymorphism). So if you need that kind of manipulation, it is preferable to switch to the most advanced type inference annotations which are described below. |
The original issue that needs to be solved when it comes to closure parameter type inference, that is to say, statically determining the types of the arguments of a closure without having to have them explicitly declared, is that the Groovy type system inherits the Java type system, which is insufficient to describe the types of the arguments.
@ClosureParams
annotationGroovy provides an annotation, @ClosureParams
which is aimed at completing type information. This annotation is primarily
aimed at framework and API developers who want to extend the capabilities of the type checker by providing type inference
metadata. This is important if your library makes use of closures and that you want the maximum level of tooling support
too.
Let’s illustrate this by fixing the original example, introducing the @ClosureParams
annotation:
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.FirstParam
void inviteIf(Person p, @ClosureParams(FirstParam) Closure<Boolean> predicate) { (1)
if (predicate.call(p)) {
// send invite
// ...
}
}
inviteIf(p) { (2)
it.age >= 18
}
1 | the closure parameter is annotated with @ClosureParams |
2 | it’s not necessary to use an explicit type for it , which is inferred |
The @ClosureParams
annotation minimally accepts one argument, which is named a type hint. A type hint is a class which
is reponsible for completing type information at compile time for the closure. In this example, the type hint being used
is groovy.transform.stc.FirstParam
which indicated to the type checker that the closure will accept one parameter
whose type is the type of the first parameter of the method. In this case, the first parameter of the method is Person
,
so it indicates to the type checker that the first parameter of the closure is in fact a Person
.
The second argument is optional and named options. It’s semantics depends on the type hint class. Groovy comes with various bundled type hints, illustrated in the table below:
Type hint | Polymorphic? | Description and examples |
---|---|---|
|
No |
The first (resp. second, third) parameter type of the method
|
|
No |
The first generic type of the first (resp. second, third) parameter of the method
Variants for |
|
No |
A type hint for which the type of closure parameters comes from the options string.
This type hint supports a single signature and each of the parameter is specified as a value of the options array using a fully-qualified type name or a primitive type. |
|
Yes |
A dedicated type hint for closures that either work on a
This type hint requires that the first argument is a |
|
Yes |
Infers closure parameter types from the abstract method of some type. A signature is inferred for each abstract method.
If there are multiple signatures like in the example above, the type checker will only be able to infer the types of
the arguments if the arity of each method is different. In the example above, |
|
Yes |
Infers the closure parameter typs from the A single signature for a closure accepting a
A polymorphic closure, accepting either a
A polymorphic closure, accepting either a
|
Even though you use FirstParam , SecondParam or ThirdParam as a type hint, it doesn’t stricly mean that the
argument which will be passed to the closure will be the first (resp. second, third) argument of the method call. It
only means that the type of the parameter of the closure will be the same as the type of the first (resp. second,
third) argument of the method call.
|
In short, the lack of the @ClosureParams
annotation on a method accepting a Closure
will not fail compilation. If
present (and it can be present in Java sources as well as Groovy sources), then the type checker has more information
and can perform additional type inference. This makes this feature particularily interesting for framework developers.
@DelegatesTo
The @DelegatesTo
annotation is used by the type checker to infer the type of the delegate. It allows the API designer
to instruct the compiler what is the type of the delegate and the delegation strategy. The @DelegatesTo
annotation is
discussed in a specific section.
Static compilation (WIP)
In the type checking section, we have seen that Groovy provides optional type checking thanks to the
@TypeChecked
annotation. The type checker runs at compile time and performs a static analysis of dynamic code. The
program will behave exactly the same whether type checking has been enabled or not. This means that the @TypeChecked
annotation is neutral with regards to the semantics of a program. Even though it may be necessary to add type information
in the sources so that the program is considered type safe, in the end, the semantics of the program are the same.
While this may sound fine, there is actually one issue with this: type checking of dynamic code, done at compile time, is by definition only correct if no runtime specific behavior occurs. For example, the following program passes type checking:
class Computer {
int compute(String str) {
str.length()
}
String compute(int x) {
String.valueOf(x)
}
}
@groovy.transform.TypeChecked
void test() {
def computer = new Computer()
computer.with {
assert compute(compute('foobar')) =='6'
}
}
There are two compute
methods. One accepts a String
and returns an int
, the other accepts an int
and returns
a String
. If you compile this, it is considered type safe: the inner compute('foobar')
call will return an int
,
and calling compute
on this int
will in turn return a String
.
Now, before calling test()
, consider adding the following line:
Computer.metaClass.compute = { String str -> new Date() }
Using runtime metaprogramming, we’re actually modifying the behavior of the compute(String)
method, so that instead of
returning the length of the provided argument, it will return a Date
. If you execute the program, it will fail at
runtime. Since this line can be added from anywhere, in any thread, there’s absolutely no way for the type checker to
statically make sure that no such thing happens. In short, the type checker is vulnerable to monkey patching. This is
just one example, but this illustrates the concept that doing static analysis of a dynamic program is inherently wrong.
The Groovy language provides an alternative annotation to @TypeChecked
which will actually make sure that the methods
which are inferred as being called will effectively be called at runtime. This annotation turns the Groovy compiler
into a static compiler, where all method calls are resolved at compile time and the generated bytecode makes sure
that this happens: the annotation is @groovy.transform.CompileStatic
.
@CompileStatic
annotationThe @CompileStatic
annotation can be added anywhere the @TypeChecked
annotation can be used, that is to say on
a class or a method. It is not necessary to add both @TypeChecked
and @CompileStatic
, as @CompileStatic
performs
everything @TypeChecked
does, but in addition triggers static compilation.
Let’s take the example which failed, but this time let’s replace the @TypeChecked
annotation
with @CompileStatic
:
class Computer {
int compute(String str) {
str.length()
}
String compute(int x) {
String.valueOf(x)
}
}
@groovy.transform.CompileStatic
void test() {
def computer = new Computer()
computer.with {
assert compute(compute('foobar')) =='6'
}
}
Computer.metaClass.compute = { String str -> new Date() }
run()
This is the only difference. If we execute this program, this time, there is no runtime error. The test
method
became immune to monkey patching, because the compute
methods which are called in its body are linked at compile
time, so even if the metaclass of Computer
changes, the program still behaves as expected by the type checker.
There are several benefits of using @CompileStatic
on your code:
-
type safety
-
immunity to monkey patching
-
performance improvements
The performance improvements depend on the kind of program you are executing. It it is I/O bound, the difference between statically compiled code and dynamic code is barely noticeable. On highly CPU intensive code, since the bytecode which is generated is very close, if not equal, to the one that Java would produce for an equivalent program, the performance is greatly improved.
Using the invokedynamic version of Groovy, which is accessible to people using JDK 7 and above, the performance of the dynamic code should be very close to the performance of statically compiled code. Sometimes, it can even be faster! There is only one way to determine which version you should choose: measuring. The reason is that depending on your program and the JVM that you use, the performance can be significantly different. In particular, the invokedynamic version of Groovy is very sensitive to the JVM version in use. |