Groovy 3.0, has a new parser that is far more flexible and maintainable than the parser in previous versions of Groovy. It’s called the Parrot parser because in the early days of creating the parser, the goal was for the new parser’s output to be an exact echo of what the old parser produced. The new parser has since been extended to support additional syntax options and language features. Some of the new features include:
-
do-while loops; enhanced (now supporting commas) classic for loops, e.g.
for(int i = 0, j = 10; i < j; i++, j--) {..}) -
lambda expressions, e.g.
stream.map(e → e + 1) -
method references and constructor references
-
try-with-resources, AKA ARM
-
code blocks, i.e.
{..} -
Java style array initializers, e.g.
new int[] {1, 2, 3} -
default methods within interfaces
-
additional places for type annotations
-
new operators: identity operators(
===,!==), elvis assignment(?=),!in,!instanceof -
safe index, e.g.
nullableVar?[1, 2] -
non-static inner class instantiation, e.g.
outer.new Inner() -
runtime groovydoc, i.e. groovydoc with
@Groovydoc; groovydoc attached to AST node as metadata
P.S. Parrot is based on the highly optimized version of antlr4(com.tunnelvisionlabs:antlr4), which is licensed under BSD.
do/while loop
Java’s class do/while loop is now supported. Example:
// classic Java-style do..while loop
def count = 5
def fact = 1
do {
fact *= count--
} while(count > 1)
assert fact == 120
Enhanced classic Java-style for loop
The more elaborate form of Java’s classic for loop with comma-separate expressions is now supported. Example:
def facts = []
def count = 5
for (int fact = 1, i = 1; i <= count; i++, fact *= i) {
facts << fact
}
assert facts == [1, 2, 6, 24, 120]
Multi-assignment in combination with for loop
Groovy has supported multi-assignment statements since Groovy 1.6:
// multi-assignment with types
def (String x, int y) = ['foo', 42]
assert "$x $y" == 'foo 42'
These can now appear in for loops:
// multi-assignment goes loopy
def baNums = []
for (def (String u, int v) = ['bar', 42]; v < 45; u++, v++) {
baNums << "$u $v"
}
assert baNums == ['bar 42', 'bas 43', 'bat 44']
Java-style array initialization
Groovy has always supported literal list/array definitions using square brackets and has avoided Java-style curly braces so as not to conflict with closure definitions. In the case where the curly braces come immediately after an array type declaration however, there is no ambiguity with closure definitions, so the Java style is now also supported.
Examples:
def primes = new int[] {2, 3, 5, 7, 11}
assert primes.size() == 5 && primes.sum() == 28
assert primes.class.name == '[I'
def pets = new String[] {'cat', 'dog'}
assert pets.size() == 2 && pets.sum() == 'catdog'
assert pets.class.name == '[Ljava.lang.String;'
// traditional Groovy alternative still supported
String[] groovyBooks = [ 'Groovy in Action', 'Making Java Groovy' ]
assert groovyBooks.every{ it.contains('Groovy') }
Java-style Lambda syntax
The Java syntax for lambda expressions is now supported.
Examples:
(1..10).forEach(e -> { println e })
assert (1..10).stream()
.filter(e -> e % 2 == 0)
.map(e -> e * 2)
.toList() == [4, 8, 12, 16, 20]
The normal variants are supported and Groovy adds additional features such as default parameter values:
// general form
def add = (int x, int y) -> { def z = y; return x + z }
assert add(3, 4) == 7
// curly braces are optional for a single expression
def sub = (int x, int y) -> x - y
assert sub(4, 3) == 1
// parameter types are optional
def mult = (x, y) -> x * y
assert mult(3, 4) == 12
// no parentheses required for a single parameter with no type
def isEven = n -> n % 2 == 0
assert isEven(6)
assert !isEven(7)
// no arguments case
def theAnswer = () -> 42
assert theAnswer() == 42
// any statement requires braces
def checkMath = () -> { assert 1 + 1 == 2 }
checkMath()
// example showing default parameter values (no Java equivalent)
def addWithDefault = (int x, int y = 100) -> x + y
assert addWithDefault(1, 200) == 201
assert addWithDefault(1) == 101
Implementation details and static optimization
For dynamic Groovy, lambda expressions are turned into equivalent Groovy closures.
So (e) → { println e } is the same as {e → println e}.
In the spirit of providing a more Java-like experience when using @CompileStatic,
we support native lambda expressions for static Groovy.
Method references
The Java 8 method reference syntax using the double colon syntax is now supported. Let’s first look at some of the supported cases before coming back to some implementation details.
The following examples illustrate referencing both static and instance methods of a class:
import java.util.stream.Stream
// class::staticMethod
assert ['1', '2', '3'] ==
Stream.of(1, 2, 3)
.map(String::valueOf)
.toList()
// class::instanceMethod
assert ['A', 'B', 'C'] ==
['a', 'b', 'c'].stream()
.map(String::toUpperCase)
.toList()
The following examples illustrate referencing methods of instance variables:
// instance::instanceMethod
def sizeAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'::length
assert sizeAlphabet() == 26
// instance::staticMethod
def hexer = 42::toHexString
assert hexer(127) == '7f'
The following examples illustrate referencing constructors:
// normal constructor
def r = Random::new
assert r().nextInt(10) in 0..9
// array constructor refs are handy when working with various Java libraries, e.g. streams
assert [1, 2, 3].stream().toArray().class.name == '[Ljava.lang.Object;'
assert [1, 2, 3].stream().toArray(Integer[]::new).class.name == '[Ljava.lang.Integer;'
// works with multi-dimensional arrays too
def make2d = String[][]::new
def tictac = make2d(3, 3)
tictac[0] = ['X', 'O', 'X']
tictac[1] = ['X', 'X', 'O']
tictac[2] = ['O', 'X', 'O']
assert tictac*.join().join('\n') == '''
XOX
XXO
OXO
'''.trim()
// also useful for your own classes
import groovy.transform.Canonical
import java.util.stream.Collectors
@Canonical
class Animal {
String kind
}
def a = Animal::new
assert a('lion').kind == 'lion'
def c = Animal
assert c::new('cat').kind == 'cat'
def pets = ['cat', 'dog'].stream().map(Animal::new)
def names = pets.map(Animal::toString).collect(Collectors.joining( "," ))
assert names == 'Animal(cat),Animal(dog)'
Implementation details and static optimization
While for the most part you can ignore implementation details, it is useful
to understand the implementation behind method references in some scenarios.
For dynamic Groovy, a method reference is implemented as a Closure method reference.
So String::toUpperCase is the same as String.&toUpperCase.
In the spirit of providing a more Java-like experience when using @CompileStatic,
we support native method references for static Groovy.
For this example (using String.transform from JDK 12):
@groovy.transform.CompileStatic
def method() {
assert 'Hi'.transform(String::toUpperCase) == 'HI'
}
The compiler will produce bytecode very similar to what Java would produce
for this case (involves INVOKEDYNAMIC, method handles and LambdaMetafactory for the bytecode geeks).
If you are already using @CompileStatic for extra compile-time type safety or performance,
then the code will be semantically equivalent but optimized similar to Java.
If you have code making use of dynamic features, then you should not use @CompileStatic with your method references, e.g.:
def convertCase(boolean upper, String arg) {
arg.transform(String::"${upper ? 'toUpperCase' : 'toLowerCase'}")
}
assert convertCase(true, 'Hi') == 'HI'
assert convertCase(false, 'Bye') == 'bye'
Since here the GString prohibits the compiler from knowing how to write the
optimized code that would be required.
Note: this example is a little contrived and could be refactored to call one
of two optimized method references but hopefully you get the idea.
The same caveat applies if you want to make use of the Closure nature behind the dynamic implementation, e.g.:
def upper = String::toUpperCase
assert upper('hi') == 'HI'
def upperBye = upper.curry('bye')
assert upperBye() == 'BYE'
!in and !instanceof operators
When wanting the negated form, rather than having to bracket expressions containing
the in and instanceof infix operators and placing the exclamation operator in
front of the brackets, an inline variant is now also supported. Examples:
/* assert !(45 instanceof Date) // old form */
assert 45 !instanceof Date
assert 4 !in [1, 3, 5, 7]
Elvis assignment operator
Groovy introduced the Elvis operator Example:
import groovy.transform.ToString
@ToString
class Element {
String name
int atomicNumber
}
def he = new Element(name: 'Helium')
he.with {
name = name ?: 'Hydrogen' // existing Elvis operator
atomicNumber ?= 2 // new Elvis assignment shorthand
}
assert he.toString() == 'Element(Helium, 2)'
Identity comparison operators
Both === and !== are supported which are the same as calling the is() method,
and negating a call to the is() method respectively.
import groovy.transform.EqualsAndHashCode
@EqualsAndHashCode
class Creature { String type }
def cat = new Creature(type: 'cat')
def copyCat = cat
def lion = new Creature(type: 'cat')
assert cat.equals(lion) // Java logical equality
assert cat == lion // Groovy shorthand operator
assert cat.is(copyCat) // Groovy identity
assert cat === copyCat // operator shorthand
assert cat !== lion // negated operator shorthand
Safe indexing
String[] array = ['a', 'b']
assert 'b' == array?[1] // get using normal array index
array?[1] = 'c' // set using normal array index
assert 'c' == array?[1]
array = null
assert null == array?[1] // return null for all index values
array?[1] = 'c' // quietly ignore attempt to set value
assert null == array?[1]
def personInfo = [name: 'Daniel.Sun', location: 'Shanghai']
assert 'Daniel.Sun' == personInfo?['name'] // get using normal map index
personInfo?['name'] = 'sunlan' // set using normal map index
assert 'sunlan' == personInfo?['name']
personInfo = null
assert null == personInfo?['name'] // return null for all map values
personInfo?['name'] = 'sunlan' // quietly ignore attempt to set value
assert null == personInfo?['name']
"var" reserved type
Groovy supports a def type placeholder.
It can be used with fields, local variables, method parameters and as a method’s return type.
In dynamic Groovy, you use def when the type is deemed not important at compile time - normal runtime typing still applies.
For static Groovy, it is used when type inference is preferred over an explicit type.
In Groovy 3.0, a new type placeholder is available: var.
It provides the syntax equivalent of Java 10’s var reserved type (but you can use it with Groovy 3 from JDK 8).
It can be used for fields, local variables and parameters.
It can also be used for lambda parameters (a Java 11 feature).
In all cases, it can be considered an alias for def.
var two = 2 // Java 10
IntFunction<Integer> twice = (final var x) -> x * two // Java 11
assert [1, 2, 3].collect{ twice.apply(it) } == [2, 4, 6]
|
ARM Try with resources
Groovy often provides better alternatives to Java 7’s try-with-resources statement for Automatic Resource Management (ARM).
That syntax is now supported for Java programmers migrating to Groovy and still wanting to use the old style:
class FromResource extends ByteArrayInputStream {
@Override
void close() throws IOException {
super.close()
println "FromResource closing"
}
FromResource(String input) {
super(input.toLowerCase().bytes)
}
}
class ToResource extends ByteArrayOutputStream {
@Override
void close() throws IOException {
super.close()
println "ToResource closing"
}
}
def wrestle(s) {
try (
FromResource from = new FromResource(s)
ToResource to = new ToResource()
) {
to << from
return to.toString()
}
}
def wrestle2(s) {
FromResource from = new FromResource(s)
try (from; ToResource to = new ToResource()) { // Enhanced try-with-resources in Java 9+
to << from
return to.toString()
}
}
assert wrestle("ARM was here!").contains('arm')
assert wrestle2("ARM was here!").contains('arm')
Which yields the following output:
ToResource closing FromResource closing ToResource closing FromResource closing
Nested code blocks
An infrequently used structure within Java is the anonymous code block. It’s generally not encouraged as it’s often a sign that refactoring the related code into a method is in order. But it’s sometimes useful to restrict scoping and is now available in Groovy:
{
def a = 1
a++
assert 2 == a
}
try {
a++ // not defined at this point
} catch(MissingPropertyException ex) {
println ex.message
}
{
{
// inner nesting is another scope
def a = 'banana'
assert a.size() == 6
}
def a = 1
assert a == 1
}
Be aware though that in Groovy having a code block looking structure after any method call will be seen as an attempt to pass a closure as the last parameter in the method call. This happens even after a new line. So it’s safe to start an anonymous code block after any other block (e.g. an if-then-else statement or another anonymous code block). Anywhere else and you might need to terminate the previous statement with a semicolon. In which case, see the note above about refactoring your code! :-)
Java-style non-static inner class instantiation
Java syntax for non-static inner class instantiation is now supported.
public class Computer {
public class Cpu {
int coreNumber
public Cpu(int coreNumber) {
this.coreNumber = coreNumber
}
}
}
assert 4 == new Computer().new Cpu(4).coreNumber
Interface default methods
Java 8 supports adding default implementations to interfaces. Groovy’s traits mechanism provides a more powerful set of OO abstractions for inheriting implementation behavior, but Java users are now familiar with default methods, so Groovy now supports the same syntax:
interface Greetable {
String target()
default String salutation() {
'Greetings'
}
default String greet() {
"${salutation()}, ${target()}"
}
}
class Greetee implements Greetable {
String name
@Override
String target() { name }
}
def daniel = new Greetee(name: 'Daniel')
assert 'Greetings, Daniel' == "${daniel.salutation()}, ${daniel.target()}"
assert 'Greetings, Daniel' == daniel.greet()
|
System properties to configure that new parser
-
groovy.antlr4can be set tofalsein Groovy 3.x to disable the new parser (set viaJAVA_OPTSif needed). This property is not needed in normal use, however, at least initially, if you have a problematic source file that doesn’t seem to work with the new parser, you may be able to revert to the old parser to compile just that file. You won’t be able to use any of the new language features with the old parser. The old parser is deprecated and will be removed in Groovy 4. -
groovy.attach.groovydoc: whether to attach groovydoc to node as metadata while parsing groovy source code(default: false) -
groovy.attach.runtime.groovydoc: whether to attach@Groovydocannotation to all members which have groovydoc(i.e./** … */) -
groovy.antlr4.cache.threshold: how frequently to clear the DFA cache, which is used to store symbol information during parsing (default: 64). The more frequently the DFA cache is cleared, the poorer parsing performance will be, but less memory will be used. The implementation may restrict the threshold to not be lower than some minimum value.
Notice: This is an advanced internal setting which affects the memory allocation behavior of the parser. You should only need to adjust this value if you are facing memory problems when compiling large Groovy files. -
groovy.clear.lexer.dfa.cache: whether to clear the DFA cache of the Groovy lexer once a threshold is reached (default: false)
Notice: This is an advanced internal setting which affects the memory allocation behavior of the parser. You should only need to adjust this value if you are facing memory problems when compiling large Groovy files.