| :source-highlighter: pygments |
| :pygments-style: emacs |
| :icons: font |
| |
| 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. |
| |
| [width="80%",align="center"] |
| |=== |
| a| NOTE: _WARNING:_ |
| Material on this page is still under development! |
| We are currently working on alpha versions of Groovy 5.0 with a goal of gathering |
| feedback on the language changes from our community. In addition, early versions |
| assist other projects and tool vendors within the Groovy ecosystem to begin assessing |
| the impact of moving to/supporting Groovy 5.0. Caution should be exercised if using |
| new features as the details may change before final release. + |
| Some features described here as "incubating" may become stable before 5.0.0 |
| final is released, others are expected to remain incubating for version 5. |
| We don’t recommend using alpha versions or incubating features for production systems. |
| We don't regard alpha versions as being feature-complete, so caution should be exercised |
| before undertaking any large scale ports to Groovy 5. Having said that, we don't |
| expect porting to Groovy 5 from Groovy 4 should involve much effort. |
| |=== |
| |
| |
| [[Groovy5.0-new]] |
| == New features |
| |
| === Support for `var` with multi-assignment |
| |
| The `var` keyword can be used in combination with multi-assignment: |
| |
| [source,groovy] |
| ---- |
| var (x, y) = [1, 2] |
| assert x == 1 && y == 2 |
| ---- |
| |
| === Extension method additions and improvements |
| |
| There are some additional extension methods for `File` objects: |
| |
| [source,groovy] |
| ---- |
| 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: |
| [source,groovy] |
| ---- |
| 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. |
| |
| [source,groovy] |
| ---- |
| 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. |
| |
| [source,groovy] |
| ---- |
| 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] |
| ---- |
| |
| [[Groovy5.0-other]] |
| == Other improvements |
| |
| [[Groovy5.0-ongoing]] |
| == 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: |
| link:https://openjdk.java.net/jeps/354[JEP 354: Switch Expressions (Second Preview)] |
| link:https://openjdk.java.net/jeps/361[JEP 361: Switch Expressions] |
| link:https://openjdk.java.net/jeps/405[JEP 405: Record Patterns & Array Patterns (Preview)] |
| link:https://openjdk.java.net/jeps/406[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: |
| https://www.python.org/dev/peps/pep-0622/[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: |
| |
| [source,groovy] |
| -------------------------------------- |
| 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: |
| |
| [source,groovy] |
| -------------------------------------- |
| 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: |
| |
| [source,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: |
| |
| [source,java] |
| -------------------------------------- |
| 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.: |
| |
| [source,java] |
| -------------------------------------- |
| 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) |
| link:https://issues.apache.org/jira/browse/GROOVY-9273[GROOVY-9273] |
| * Use of "_" (underscore) for unused parameters (see "Treatment of underscores" in https://openjdk.java.net/jeps/302[JEP 302: Lambda Leftovers]) |
| |
| [[Groovy5.0-breaking]] |
| == Other breaking changes |
| |
| * Scripts containing a static `main` method and no statements outside that method have changed slightly |
| for improved JEP 445 compatibility. The script class for such methods no longer extends `Script` and |
| hence no longer has access to the script context or bindings. For many such scripts, access to the |
| binding isn't needed and there is now a simpler structure for those scripts. Scripts which need access |
| to the binding should instead use a no-arg instance `run` method. |
| (link:https://issues.apache.org/jira/browse/GROOVY-11118[GROOVY-11118]) |
| * The `getProperty` method allows for getting properties that don't exist within a class. |
| Previously, static properties from an outer class were given priority over overrides |
| by `getProperty`. This is in conflict with the priority given to outer classes in other places. |
| (link:https://issues.apache.org/jira/browse/GROOVY-10985[GROOVY-10985]) |
| * The minus operator for sets in Groovy was subject to an existing |
| https://bugs.openjdk.org/browse/JDK-6394757[JDK bug] |
| in the JDK's `AbstractSet#removeAll` method. The behavior now confirms |
| with the behavior of the fix being proposed for that bug. |
| If for some strange reason you rely on the buggy behavior, you can use |
| the `removeAll` method directly rather than the `minus` operator (at least until it is fixed in the JDK). |
| (link:https://issues.apache.org/jira/browse/GROOVY-10964[GROOVY-10964]) |
| * Groovy 4 had a `$getLookup` method used to work around stricter JPMS access requirements. |
| Groovy no longer needs this hook. This method is not normally visible or of use to |
| typical Groovy users but if framework writers are making use of that hook, |
| they should rework their code. |
| (link:https://issues.apache.org/jira/browse/GROOVY-10931[GROOVY-10931]) |
| * Groovy was incorrectly setting a null default value for annotations |
| without a default value. If framework writers have made use of, |
| or coded around the buggy behavior, they may need to rework their code. |
| It might mean simplification by removing a workaround. |
| (link:https://issues.apache.org/jira/browse/GROOVY-10862[GROOVY-10862]) |
| * Some Groovy AST transform annotations, like `@ToString` were given |
| `RUNTIME` retention even though Groovy itself and typical Groovy user |
| behavior never needs access to that annotation at runtime. This was |
| done with a view that perhaps some future tools or framework might |
| be able to use that information in some useful way. We know of no such |
| frameworks or tools, so we have changed the retention to `SOURCE` to |
| give cleaner class files. |
| (link:https://issues.apache.org/jira/browse/GROOVY-10862[GROOVY-10862]) |
| * Groovy's `%` operator is called the "remainder" operator. Informally, |
| it is also known as the "mod" operator and indeed, for operator overloading |
| purposes we have historically used the `mod` method. While this name is in |
| part just a convention, it can cause some confusion, since for example, |
| the `BigInteger` class has both `remainder` and `mod` methods and |
| our behavior, like Java's, follows the behavior of the `remainder` method. |
| In Groovy 5, operator overloading for `%` is now handled by the `remainder` method. |
| Fallback behavior is supported and workarounds exist for folks already using the `mod` method. |
| (link:https://issues.apache.org/jira/browse/GROOVY-10800[GROOVY-10800]) |
| * Improvements have been made to better align how method selection |
| is performed between the dynamic Groovy runtime and with static compilation. |
| (link:https://issues.apache.org/jira/browse/GROOVY-8788[GROOVY-8788]) |
| * Improvements have been made to accessing fields within Maps. |
| (link:https://issues.apache.org/jira/browse/GROOVY-6144[GROOVY-6144], |
| link:https://issues.apache.org/jira/browse/GROOVY-5001[GROOVY-5001]) |
| |
| [[Groovy5.0-requirements]] |
| == JDK requirements |
| |
| Groovy 5 requires JDK16+ to build and JDK11 is the |
| minimum version of the JRE that we support. |
| Groovy 5 has been tested on JDK versions 11 through 20. |
| |
| [[Groovy5.0-more-info]] |
| == More information |
| |
| You can browse all the link:../changelogs/changelog-5.0.0-unreleased.html[tickets closed for Groovy 5.0 in JIRA]. |