Groovy: Neue Sprachelemente

Die Sprache Groovy umfasst einige neue syntaktische Elemente, die es in Java nicht in dieser Form gibt. Im Wesentlichen dienen sie dazu, den Schreibaufwand bei häufig vorkommenden Programmierproblemen zu vermindern und die Programme übersichtlicher zu machen. Sie können ohne Weiteres Groovy-Programme schreiben, die von diesen Möglichkeiten keinen Gebrauch machen, in der Praxis der Groovy-Programmierung spielen sie jedoch eine wichtige Rolle, da sie einen wesentlichen Vorteil des Arbeitens mit Groovy gegenüber Java darstellen.

Wir werden auf die in diesem Abschnitt angesprochenen Themen in den folgenden Kapiteln noch ausführlicher eingehen, es ist aber hilfreich, wenn Sie schon mal einen Überblick haben und die wichtigsten Begriffe kennen, um das Verständnis des Folgenden zu erleichtern.

Neue Schlüsselwörter

Bearbeiten

Groovy kennt einige neue Schlüsselwörter, die es in Java nicht gibt.

Das Schlüsselwort in dient als Operator für die Prüfung, ob ein Objekt in einem Behälterobjekt, zum Beispiel einer Collection, enthalten ist oder nicht. Dieser Operator wird durch die Methode contains() implementiert. Außerdem wird das Wort in einer Form der for-Schleife benutzt.

if (zeichen in ['A','B','C']) ...
for (zeichen in ['A','B','C']) { ... }

Das Schlüsselwort as ist ein Operator für die explizite Typanpassung. Er bewirkt, dass Groovy versucht, den vor as stehenden Wert auf den dahinter angegebenen Typ abzubildenden. Da Groovy weitgehende Möglichkeiten der Typanpassung bietet, kann dieser Operator in den verschiedensten Situationen eingesetzt werden, zum Beispiel bei der Initialisierung von Arrays mit Hilfe von Listen oder für die Übergabe von primitiven Variablen an Java-APIs. In vielen Fällen genügt allerdings auch die automatische Typanpassung durch Groovy.

stringArray = ['A','B','C'] as String[] // initialisierung eines Arrays
javaObjekt.aufruf (zahl as int) // Übergabe der Zahl als int statt Integer

Dieses Schlüsselwort dient dazu, untypisierte Variablen und Methoden zu definieren. Es wird anstelle eines Typbezeichners vor den Namen des zu definieren Elements gesetzt, wenn der Compiler anders nicht erkennen kann, dass hier etwas definiert werden soll:

def counter // Definition einer untypisierten Variablen
def count() { ... } // Definition einer untypisierten Methode

Unterstützte Typen

Bearbeiten

Groovy unterstützt einige häufig benutzte Java-Standardtypen direkt in der Sprache durch Initialisierungskonstrukte und Operatoren.

Zur Initialisierung einer Liste kann eine Aufzählung der Elemente in eckigen Klammern verwendet werden; sie erzeugt ein Objekt vom Typ java.util.ArrayList.

neueListe = ['alpha','beta','gamma','delta']
leereListe = []

Auf die Elemente einer Liste greifen Sie wie auf die Elemente eines Arrays zu.

zweitesMitIndex2 = neueListe[2] // entspricht neueListe.get(2)

Sie können auch mehrere Indices oder ganze Wertbereiche angeben, in diesem Fall ist das Ergebnis jedoch wieder eine Liste und nicht ein einzelnes Element der Liste.

println neueListe[1,2]    // ergibt ['beta','gamma']
println neueListe[2,1]    // ergibt ['gamma','beta']
println neueListe[1..3]   // ergibt ['beta','gamma','delta']

Darüber hinaus akzeptiert Groovy auch negative Indices; sie werden vom Ende der Liste an rückwärts gezählt – liste[x] entspricht bei einem negativen x also dem Ausdruck liste[x+liste.length()].

println neueListe[-1]     // ergibt 'delta'

Einzelne indizierte Werte können auch schreibend benutzt werden:

neueListe[4] = 'epsilon'

Diese neuen Indizierungsmöglichkeiten gibt es für alle Objekte, deren Klassen das Interface List implementieren (z.B. ArrayList und Vector), sowie auch für alle Arrays.

Eine Map initialisieren Sie ebenfalls mit einer Aufzählung der Elemente in eckigen Klammern, dabei müssen aber die Schlüssel und Werte der Elemente durch einen Doppelpunkt getrennt angegeben werden. Eine leere Map wird durch einen in eckige Klammern eingeschlossenen Doppelpunkt erzeugt. Das Ergebnis ist immer eine Instanz von java.util.LinkedHashMap.

neueMap = ['alpha':'α', 'beta':'β', 'gamma':'γ', 'delta':'δ']
leereMap = [:]

Auch auf Maps können Sie wie auf Arrays zugreifen; daher bezeichnet man sie auch als assoziative Arrays. Allerdings muss hier der Schlüsselwert als Index angegeben werden:

buchstabeBeta = neueMap['beta']
neueMap['epsilon'] = 'ε'

Wenn der Schlüsselwert ein String ist, der den Regeln der Namensbildung in Groovy (wie in Java) entspricht, können Sie darauf auch so zugreifen:

buchstabeBeta = neueMap.beta
neueMap.epsilon = 'ε'

Reguläre Ausdrücke

Bearbeiten

Ein Pattern-Objekt erzeugen Sie in Groovy sehr einfach mit Hilfe des Operators ~ (Tilde-Zeichen) aus einem String. Sie können sich dabei zunutze machen, dass in Groovy auch Strings mit einem Schrägstrich als Begrenzungszeichen definiert werden können; in diesem Fall werden die Backslash-Zeichen im String nicht interpretiert.

muster = ~/\w.+\w/      // neues Pattern-Objekt

Außerdem gibt es eine Reihe von Operatoren für reguläre Ausdrücke, beispielsweise den Muster-Vergleichsoperator ==~:

if (wort ==~ /[A-Za-z]*/) ...     // Besteht Wort nur aus Buchstaben?

Closures

Bearbeiten

Die Closure ist ein (in der Java-Welt) neues Konstrukt, das seine Wurzel in der funktionalen Programmierung hat und einen starken Einfluss auf den Programmierstil ausübt. Mittels einer Closure kann man gewissermaßen ein Stück Programmcode in ein Objekt verpacken und es einer Variablen zuweisen oder als Argument einer Methode übergeben. Damit bieten Closures einen einfacher zu handhabenden Ersatz für die häufigsten Anwendungsfälle von inneren Klassen. Closures bieten aber darüber hinaus eine Fülle von Möglichkeiten und werden auch in der Groovy-Standardbibliothek sehr häufig angewendet.

Hier ist ein Beispiel für die Zuweisung einer Closure an eine Variable; im ersten Fall hat sie einen explizit benannten Parameter, im zweiten Fall wird ein implizit definierter Parameter namens it verwendet.

c1 = { x -> println x }
c2 = { println it }

Die Methode each() ist in Groovy für alle Objekte vordefiniert, die das Interface Collection implementieren; sie nimmt eine Closure als Argument an und ruft diese für alle enthaltenen Elemente einmal auf:

myCollection.each (c1)       // Gibt alle Elemente der Reihe nach aus

Wenn die Closure direkt im Methodenaufruf definiert wird, verkürzt sich der Aufruf folgendermaßen:

myCollection.each { println it }

Ein typischer Anwendungsfall für Closures ist das Event-Handling, das in Java-Programmen in der Regel mittels anonymer innerer Klassen implementiert wird. Das folgende Beispiel zeigt einen Java-Ausschnitt, das einen Button mit ActionListener definiert.

// Java
JButton b = new JButton("OK");
b.addActionListener ( new ActionListener() {
  public void actionPerformed(ActionEvent e) {
     System.exit(0);
  }
});

Mittels einer Closure sieht das in Groovy viel einfacher aus:

// Groovy
JButton b = new JButton("OK")
b.addActionListener ( { System.exit(0) } as ActionListener)

Der Abschnitt Closures geht ausführlich auf die Funktionsweise von Closures und ihre Nutzung ein.

Wertebereiche (Ranges)

Bearbeiten

Groovy unterstützt einen speziellen Listentyp, mit dem Zahlenfolgen im Abstand 1 mit durch ihre Ober- und Untergrenzen dargestellt werden können. Es gibt ihn in einer inklusiven und einer rechts-exklusiven Variante:

intervall1 = 1..5     // Enthält die Werte 1,2,3,4,5
intervall2 = 1..<5    // Enthält die Werte 1,2,3,4

Ein Wertebereich implementiert das Interface List und kann damit überall eingesetzt werden, wo im Programm eine Liste benötigt wird, die durch den Anfangs- und den Endwert definiert ist. Das folgende Beispiel liefert den zehnten Buchstaben des Alphabets:

zehnterBuchstabe = ('A'..'Z')[9]

Ein anderes Beispiel ist die Programmierung von Zählschleifen; siehe Programmlogik.