Release notes for Groovy 5.0
Groovy 5 builds upon existing features of earlier versions of Groovy. In addition, it incorporates numerous new features and streamlines various legacy aspects of the Groovy codebase.
|
New features
Extension method additions and improvements
There are some additional extension methods for File
objects:
def myscript = new File('MyScript.groovy')
assert myscript // Groovy truth: true if the file exists
assert myscript.extension == 'groovy'
assert myscript.baseName == 'MyScript'
And similar methods for Path
objects:
def mypic = path.resolve('MyFigure.png')
assert mypic // Groovy truth: true if the file exists
assert mypic.extension == 'png'
assert mypic.baseName == 'MyFigure'
There are additional variants of collectEntries
for arrays, iterables and iterators
with separate functions for transforming the keys and values. There are variants
with and without collectors.
There are also variants which transform just the key or value.
The withCollectedKeys
method collects key/value pairs for each item with the
item as the value and the key being the item transformed by the supplied function.
The withCollectedValues
method collects key/value pairs for each item with the
item as the key and the value being the item transformed by the supplied function.
def languages = ['Groovy', 'Java', 'Kotlin', 'Scala']
def collector = [clojure:7]
assert languages.collectEntries(collector, String::toLowerCase, String::size) ==
[clojure:7, groovy:6, java:4, kotlin:6, scala:5]
assert languages.withCollectedKeys(s -> s.take(1)) ==
[G:'Groovy', J:'Java', K:'Kotlin', S:'Scala']
assert languages.withCollectedValues(s -> s.size()) ==
[Groovy:6, Java:4, Kotlin:6, Scala:5]
There are also equivalent variants for maps. The collectEntries
method
takes separate functions for transforming the keys and values.
The collectKeys
and collectValues
variants take a single function
for transforming just the keys and values respectively.
def lengths = [Groovy:6, Java:4, Kotlin:6, Scala:5]
assert lengths.collectEntries(String::toLowerCase, { it ** 2 }) ==
[groovy:36, java:16, kotlin:36, scala:25]
assert lengths.collectKeys{ it[0] } == [G:6, J:4, K:6, S:5]
assert lengths.collectValues(Math.&pow.rcurry(2)) ==
[Groovy:36.0, Java:16.0, Kotlin:36.0, Scala:25.0]
assert lengths.collectValues(Math.&pow.curry(2).memoize()) ==
[Groovy:64.0, Java:16.0, Kotlin:64.0, Scala:32.0]
Other improvements
Ongoing work
Enhanced switch (under investigation)
Groovy has always had a very powerful switch statement. The statement could be made more powerful, e.g. support destructuring, and could be supported in contexts where expressions are expected.
As inspiration, Java has made, or is investigating future enhancements including switch expressions and other related enhancements: JEP 354: Switch Expressions (Second Preview) JEP 361: Switch Expressions JEP 405: Record Patterns & Array Patterns (Preview) JEP 406: Pattern Matching for switch (Preview) We should investigate these proposals both in terms of enhancing the existing Groovy switch but also in terms of deciding which syntax from Java we might like to support in the future.
Other languages like Python are also improving their switch statements: PEP 622 — Structural Pattern Matching. We should investigate whether any features of their design make sense for Groovy’s dynamic nature.
As an example of destructuring, instead of the following existing code:
def make3D(pt) {
switch(pt) {
case Point3D:
return pt
case Point2D:
return new Point3D(pt.x, pt.y, 0)
case List:
def (x, y, z) = pt
if (x == 0 && y == 0 && z == 0)
throw new IllegalArgumentException("Origin not allowed")
return new Point3D(x, y, z)
...
}
}
You could use something like:
def make3D(pt) {
switch(pt) {
case Point3D:
return pt
case Point2D(x, y):
return new Point3D(x, y, 0)
case [0, 0, 0]:
throw new IllegalArgumentException("Origin not allowed")
case [x, y, z]:
return new Point3D(x, y, z)
...
}
}
An example of guarded patterns being considered for Java:
static void testTriangle(Shape s) {
switch (s) {
case null ->
System.out.println("Null!");
case Triangle t && (t.calculateArea() > 100) ->
System.out.println("Large triangle");
case Triangle t ->
System.out.println("Small triangle");
default ->
System.out.println("Non-triangle");
}
}
Another destructuring example:
int eval(Expr n) {
return switch(n) {
case IntExpr(int i) -> i;
case NegExpr(Expr n) -> -eval(n);
case AddExpr(Expr left, Expr right) -> eval(left) + eval(right);
case MulExpr(Expr left, Expr right) -> eval(left) * eval(right);
default -> throw new IllegalStateException();
};
}
We should consider the currently proposed nested record pattern when exploring our destructuring options, e.g.:
static void printColorOfUpperLeftPoint(Rectangle r) {
if (r instanceof Rectangle(ColoredPoint(Point p, Color c), ColoredPoint lr)) {
System.out.println(c);
}
}
Other Java-inspired enhancements
-
Module definitions written in Groovy (i.e. module-info.groovy) GROOVY-9273
-
Use of "_" (underscore) for unused parameters (see "Treatment of underscores" in JEP 302: Lambda Leftovers)
Other breaking changes
-
Numerous classes previously "leaked" ASM constants which are essentially an internal implementation detail by virtue of implementing an
Opcodes
interface. This will not normally affect the majority of Groovy scripts but might impact code which manipulates AST nodes such as AST transforms. Before compiling with Groovy 4, some of these may need one or more appropriate static import statements added. AST transforms which extendAbstractASTTransformation
are one example of potentially affected classes. (GROOVY-9736).
JDK requirements
Groovy 5.0 requires JDK16+ to build and JDK8 is the minimum version of the JRE that we support. This may change before the GA version of Groovy 5 is released. Groovy has been tested on JDK versions 8 through 17.
More information
You can browse all the tickets closed for Groovy 5.0 in JIRA.