Programmiersprache: Apache Groovy 4 übernimmt Java-Neuerungen

Die JVM-Sprache führt einige Java-Sprachfeatures der letzten Jahre wie Switch Expressions und Records ein. Außerdem erhält sie eine eigene Abfragesprache.

In Pocket speichern vorlesen Druckansicht 78 Kommentare lesen

(Bild: Kenneth Summers/Shutterstock.com)

Lesezeit: 5 Min.
Inhaltsverzeichnis

Zwei Jahre nach Apache Groovy 3.0 ist Version 4 der Programmiersprache erschienen. Die frische Hauptversion der JVM-Sprache (Java Virtual Machine) führt einige Java-Sprachfeatures der letzten Jahre ein, wenn auch mit leichten Variationen. Außerdem bekommt sie mit GINQ eine eigene Abfragesprache und setzt auf Design by Contract.

Jenseits der sprachlichen Neuerungen verabschiedet sich das Release von den Legacy-Paketen, die als Dubletten zu den in Groovy 3 neu eingeführten Paketen für das Zusammenspiel mit dem Java Platform Module System (JPMS) existierten. Das Nebeneinander sollte einen sanften Umstieg auf die neuen Konventionen gewährleisten. Details zu den alten und neuen Namen finden sich in den Release Notes zur Version 3.0.

Java hat in Version 13 die Switch Expressions als Ergänzung zu Switch Statements eingeführt und in Java 14 stabilisiert. Die nun in Groovy umgesetzte Variante des Sprachkonstrukts ist an die Vorgehensweise von Java angelehnt. Während Switch Statements für jeden Fall ein case mit einem : und anschließendem Code haben, können Switch Expressions unter anderem einer Variable direkt einen Wert zuweisen. Nach dem case steht üblicherweise jeweils ein ->. Viele typische switch-Blöcke werden damit übersichtlicher:

// Switch Statement
def result
switch(i) {
  case 0: result = 'Null'; break
  case 1: result = 'Eins'; break
  case 2: result = 'Zwei'; break
  default: throw new IllegalStateException('unbekannt')
}

// Switch Expression
def result = switch(i) {
    case 0 -> 'Null'
    case 1 -> 'Eins'
    case 2 -> 'Zwei'
    default -> throw new IllegalStateException('unbekannt')
}

Hinter -> muss genau ein einzelner Ausdruck stehen. Es lassen sich jedoch mehrere Anweisungen in einem Block in geschweiften Klammern kombinieren. Daneben ist es möglich Switch Expressions in der alten Doppelpunktschreibweise zu programmieren. Dafür muss jeder Fall ein yield enthalten, um der Variable den Wert zuzuweisen. Das Vermischen der beiden Formen mit : und -> ist nicht erlaubt.

Im Gegensatz zu Java müssen in der derzeitigen Umsetzung nicht alle Fälle abgedeckt sein. Wenn keine case-Zeile den Wert widerspiegelt und kein default existiert, fügt Groovy implizit null als Standardwert hinzu. Allerdings sind wohl schon strikte Vorgaben für eine verpflichtende Abdeckung aller Fälle und das Vermeiden von null in Planung.

Sealed Classes existieren in der Java-Welt seit Version 15, und seit Java 17 gelten sie als stabil. Groovy erlaubt nun ebenfalls das Versiegeln von Klassen, Interfaces und Traits, um festzulegen, welche Typen als Kinder in Frage kommen. Das Versiegeln erfolgt über das Schlüsselwort sealed oder die Annotation @Sealed. Die erlaubten Kindtypen lassen sich explizit über permits beziehungsweise permittedSubclasses festlegen. Zusätzlich erlaubt der Compiler implizit die in derselben Datei definierten Untertypen.

Im Zusammenspiel mit Java ab der aktuellen Version 17 erstellt Groovy den Bytecode als Sealed Classes, die zu denen in Java kompatibel sind. Für ältere Java-Varianten erkennt zwar der Groovy-Compiler die versiegelten Typen, der Java-Compiler behandelt sie aber wie reguläre. In Groovy 4.0 sind Sealed Classes als incubating markiert. Damit sind potenziell noch Änderungen an der konkreten Umsetzung möglich.

Records hatten in Java 14 ihren ersten Auftritt, und sie gelten seit Java 16 als stabil. Sie dienen als objektorientiertes Konstrukt zum Speichern simpler Werte in Form von Immutable Data, also unveränderlichen Daten. Groovy kennt bereits seit geraumer Zeit die Annotation @Immutable für unveränderliche Inhalte.

Mit dem aktuellen Release führt Groovy das Schlüsselwort record und die Annotation @RecordType ein, die im Zusammenspiel mit Java ab Version 16 als native Records umgesetzt sind. Wie bei den Sealed Types gilt für frühere Java-Varianten eine emulierte Umsetzung, die sich zwar ebenso verhalten, die der Java-Compiler aber nicht als Records erkennt.

Eine weitere als incubating markierte Neuerung ist die Abfragesprache Groovy-Integrated Query (GINQ). Mit ihr lassen sich Abfragen auf Collections in ähnlicher Weise wie mit SQL erstellen, wie folgendes Codebeispiel aus den Release Notes zeigt:

from p in persons
leftjoin c in cities on p.city.name == c.name
where c.name == 'Shanghai'
select p.name, c.name as cityName

from p in persons
groupby p.gender
having p.gender == 'Male'
select p.gender, max(p.age)

from p in persons
orderby p.age in desc, p.name
select p.name

from n in numbers
where n > 0 && n <= 3
select n * 2

from n1 in nums1
innerjoin n2 in nums2 on n1 == n2
select n1 + 1, n2

Die Groovy-Dokumentation zeigt neben der Beschreibung von GINQ weitere Beispiele auf.

Ebenfalls neu und incubating ist das Modul groovy.contracts, das eine Programmierung nach dem Design-by-Contract-Prinzip (DbC) einführt. Die Vorgehensweise stammt aus der Programmiersprache Eiffel und setzt auf eine Art Verträge zum Verwenden der Schnittstellen, die das Zusammenspiel der einzelnen Programmmodule sicherstellen sollen. Für C++ waren Contracts eigentlich zu C++20 geplant, inzwischen ist jedoch fraglich, ob sie es zumindest in das kommende C++23 schaffen.

Die Verträge im DbC legen die Vorbedingungen und die Nachbedingungen fest. Der Aufrufer muss Erstere einhalten und der Aufgerufene Letztere. Als dritter Baustein beschreiben die Invarianten Bedingungen, die beim Ausführen immer gelten müssen. Groovy bietet für die drei Elemente eigene Annotationen. Eine Umsetzung findet sich in folgendem Codebeispiel aus den Release Notes:

import groovy.contracts.*

@Invariant({ speed() >= 0 })
class Rocket {
    int speed = 0
    boolean started = true

    @Requires({ isStarted() })
    @Ensures({ old.speed < speed })
    def accelerate(inc) { speed += inc }

    def isStarted() { started }

    def speed() { speed }
}

def r = new Rocket()
r.accelerate(5)

Für Groovy existiert bereits seit längerer Zeit mit GContracts ein externes Projekt für Design by Contract, das nicht weiterentwickelt wird und archiviert ist.

Weitere Neuerungen in Groovy 4 wie die JavaShell und POJO-Annotationen (Plain Old Java Object) für eine schlankere Umsetzung von statischen Groovy-Objekten lassen sich den Release Notes entnehmen. Die Download-Seite verweist auf den Sourcecode sowie Binaries für unterschiedliche Betriebssysteme.

(rme)