Groovy für Grails-Entwickler

Wer die Web-Entwicklungsumgebung Grails kennen lernen möchte, sollte sich etwas mit der Programmiersprache Groovy auskennen, denn Grails beruht auf Groovy, und Groovy ist ein wesentlicher Faktor für die in der Java-Welt unübertroffene Effektivität, die Grails bei der Realisierung von Web-Anwendungen ermöglicht. Diese kurze Einführung will Sie mit den elementaren Grundlagen bezüglich Groovy vertraut machen, die erforderlich sind, um mit Grails für's Erste zurecht zu kommen.

Groovy für Java EntwicklerWenn Sie intensiver mit Grails arbeiten wollen, wird es Ihnen nicht erspart bleiben, sich etwas tiefer mit Groovy und seinen speziellen Fähigkeiten auseinander zu setzen, als es diese Übersicht erlaubt. Mehr Informationen dazu finden Sie unter http://groovy.codehaus.org; zu Empfehlen ist aber in jedem Fall auch die Lektüre eines einschlägigen Fachbuchs, z.B. Groovy für Java-Entwickler.

Stand: Groovy 1.5

Was ist Groovy?

Groovy wird häufig als Skriptsprache bezeichnet und in einem Atemzug mit JRuby, Jython, BeanShell usw. genannt. Dies ist aber nicht ganz zutreffend, denn Groovy kompiliert alle Programme zu Java-Klassen, die sich nahtloas mit normalen, mit Java programmierten Anwendungen integrieren lassen. Allerdings können Sie Groovy-Programme fast wie Skripte verwenden, also einfach in eine Textdatei schreiben und ausführen, ohne sie erst in einem gesonderten Schritt in eine .class-Datei kompilieren zu müssen. Von dieser Fähigkeit machen wir im Zusammenhang mit Grails aber keinen Gebrauch, hier werden die Programme in der Regel vor ihrer Ausführung erst einmal kompiliert – wovon sie allerdings kaum etwas merken.

Die Sprache Groovy basiert bezüglich ihrer Syntax weitestgehend auf Java. Die meisten Java-Programme können unverändert unter Groovy starten. Wenn Sie die besonderen Möglichkeiten von Groovy nutzen wollen, müssen Sie allerdings etwas anders programmieren, als Sie es mit Java gewohnt sind. Die folgende Übersicht kann sich aber darauf beschränken, die wesentlichen Unterschiede zwischen Groovy und Java aufzuzeigen.

Kleine und große Erleichterungen

Was Ihnen sicher als Erstes an Groovy auffällt, sind eine Reihe kleiner syntaktischer und anderer Änderungen gegenüber Java, die dafür sorgen, dass Sie weniger tippen müssen und dass der Code etwas aufgeräumter aussieht. Diese Abweichungen sind leicht verständlich und es genügt daher, sie hier kurz aufzuführen.

Standard-Imports. Die wichtigsten Standardbibliotheken importiert Groovy automatisch, so dass sie nicht in eigenen import-Anweisungen aufgeführt werden. Dazu gehören (neben dem auch in Java immer verfügbaren java.lang.*):

  • java.io.*
  • java.math.BigDecimal
  • java.math.BigInteger
  • java.net.*
  • java.util.*
  • groovy.lang.*
  • groovy.util.*

Semikolon am Zeilenende. Sie können das Semikolon zum Abschluss einer Anweisung weglassen, wenn es mit dem Zeilenende zusammen trifft. Als Folge muss man allerdings etwas aufpassen, dass Zeilenumbrüche innerhalb von Anweisungen nicht an Stellen auftreten, wo auch die jeweilige Anweisung syntaktisch gesehen zu ende sein könnte.

println ("Erste Zeile");
println ("Zweite Zeile") // ohne Semikolon geht's auch

Parameter-Klammern. In bestimmten Fällen können auch die Klammern um Methodenparameter weggelassen werden. Diese Möglichkeit besteht, wenn der Methodenaufruf nicht Teil eines Ausdrucks ist und das erste Argument nicht selbst ein geklammerter Ausdruck ist; man sollte aber nur in sehr einfachen und durchschaubaren Fällen von ihr Gebrauch machen. (Bei Konstruktoren können Sie die Klammern nie weglassen.)

println "Dritte Zeile"

Return am Methodenende. Auch das explizite return-Statement am Ende einer Methode kann entfallen. In diesem Fall wird einfach das Ergebnis der letzten Anweisung vor dem Rücksprung bzw. null als Rückgabewert verwendet.

Standard-Sichtbarkeit. Im Gegensatz zu Java sind Klassen, Interfaces und Methoden public, wenn kein Sichtbarkeits-Qualifikator angegeben ist. Felder werden generell anders behandelt (siehe unten, GroovyBeans). Package-private Deklarationen gibt es in Groovy nicht.

// Öffentliche Klassendefinition
class EineKlasse {
    // Öffentliche Methode, liefert "ein Text"
    String toString { "Ein Text" }
}

Gleichheit. Die Operatoren == und != beziehen sich immer auf Gleichheit im Sinne der equals()-Methode, nicht auf die Objektidentität. Deshalb kann ein Ausdruck wie a==b auch ein String-Vergleich sein. Um auf Objektidentität zu prüfen, muss man stattdessen a.is(b) verwenden.

if (text=="") { . . . } //Prüft auf Leerstring

Wahrheitsbegriff. Die Parameter für if, while und andere Anweisungen, die eine Wahrheitswert erfordern, müssen bei Groovy keine Booleschen Werte sein. Für Groovy sind folgende Werte falsch: null, false und Boolean.valueOf(false), Zahl mit dem Wert 0, leerer String, leere Kollektion, leere Map, leerer Matcher sowie Iterator und Enumeration ohne weitere Elemente. Alles andere ist wahr.

if (! text) { . . . } // Prüft auch auf null oder Leerstring

Zusicherungen Die assert-Anweisung hat in Groovy eine geringfügig abweichende Syntax (Komma statt Doppelpunkt vor dem Erläuterungstext).

assert param, "param darf nicht null oder leer sein"

Die Zusicherungen können in Groovy nicht deaktiviert werden, da sie ein wichtiges Mittel zur Konsistenzsicherung dynamischer Programme sind.

Benannte Argumente. Bei Methoden und Konstruktoren können benannte Argumente übergeben werden. Da der Java-Binärcode so etwas eigentlich nicht zulässt, sind dazu allerdings Tricks erforderlich. Bei Methodenaufrufen werden die Argumente als Map übergeben, daher muss die Methode mit einer Map als Parameter definiert sein.

Aufruf einer Methode mit benannten Parametern:

def result = Aufgabe.findAllWhere(benutzer:b1)

Die zugehörige Methode könnte so definiert sein:

def findAll (Map args) { ... }

Bei Konstruktoren werden die benannte Argumente in Setter-Aufrufe umgewandelt; dies funktioniert allerdings nur, wenn die betreffende Klasse einen Default-Konstruktor (oder gar keinen Konstruktor) hat. Namen und Werte der Parameter müssen wie bei Map-Literalen (siehe oben) durch einen Doppelpunkt getrennt werden.

Folgender Konstruktor-Aufruf setzt automatisch die Properties bezeichnung und termin:

def a = new Aufgabe(
      bezeichnung: "Schreibtisch aufräumen",
      termin: new Date()+10))

Vorgabewerte für Parameter Bei der Definition einer Methode können Sie allen oder den letzten Parametern Vorgabewerte zuordnen, die diese automatisch erhalten, wenn für den betreffenden Parameter kein Argument angegeben ist. Der Vorgabewert folgt dem Betreffenden Parameter, durch Gleichheitszeichnen getrennt, in der Parameterliste.

def createAufgabe(
        String bezeichnung, 
        termin=new Date()+10) {
  . . .
}

Checked Exceptions. In Groovy-Programmen gibt es keine Checked-Exceptions mehr; vielmehr werden alle Exceptions wie Runtime-Exceptions unter Java behandelt. Demzufolge können Sie die throws-Klauseln in den Methodenköpfen weglassen und try-catch-Blöcke nur dann benutzen, wenn sie wirklich benötigt werden.

Skripte

Um Groovy-Programme zu schreiben, brauchen Sie nicht unbedingt Klassen und Methoden zu definieren; noch nicht einmal eine main()-Methode ist erforderlich. Schreiben Sie im Quellprogramm einfach die auszuführenden Anweisungen auf, als befänden sie sich innerhalb des Rumpfes einer Instanzmethode. So ist die folgenden Zeile ein vollständiges Groovy-Programm:

println "Aktuelle Zeit: " + new Date()

Wenn Sie diese Zeile in eine Datei namens zeit.groovy speichern und Groovy korrekt installiert haben, können Sie sie aus der Konsole im selben Verzeichnis so aufrufen:

> groovy zeit.groovy

Ein solches Programm bezeichnen wir als Skript. Groovy übersetzt es vor der Ausführung alledings in eine Java-Bytecode-Klasse, die entweder den Namen der Quellcode-Datei oder einen generischen Namen erhält.

Innerhalb eines Skripts kann man Variablen verwenden, ohne sie zu definieren. Weisen sie ihr einfach einen Wert zu, und die Variable ist definiert.

z = new Date()
println z

Sie können innerhalb einer Skript-Quelldatei auch einzelne Methoden definieren (sie werden hier Funktionen genannt), die innerhalb des ganzen Skripts verfügbar sind.

Außerdem ist es auch möglich, in einer Skript-Datei zusätzlich ganz normale Klassen zu definieren. Diese werden gesondert kompiliert und können nicht direkt auf die Skriptvariablen zugreifen. Außerdem solche Klassen nicht denselben Namen haben wie die Skriptdatei.

GroovyBeans

Groovy erspart Ihnen einige Schreibarbeit. Sehen Sie sich diese sehr schlichte Domain-Klasse an:

class Person {
    String name
    Date geburtsdatum
    int punkte
}

Sie sieht aus, als hätte sie zwei package protected Felder. In Groovy werden aber alle Felder, die nicht mit einem Sichtbarkeits-Qualifikator wie public oder private versehen sind, zu Properties, d.h. zu privaten Feldern, zu denen der Compiler automatisch passende öffentliche Getter und Setter – hier also getName(), setName() usw. - hinzugeneriert. Wenn Sie Zugriffsmethoden benötigen, die mehr machen sollen als nur die Werten zu auszulesen und zu setzen, können Sie sie immer noch selbst definieren. In vielen Fällen genügt es aber, einfach wie oben die Feldnamen mitsamt Typ aufzuführen.

Umgekehrt brauchen Sie auch keine Getter oder Setter benutzen, wenn Sie auf Properties von Bean-Instanzen zugreifen wollen. Stattdessen können Sie so vorgehen, als hätten Sie es mit öffentlichen Feldern des Objektes zu tun.

def p = new Person (name:'Klaus', ... )
println p.name  // entspricht p.getName()

Das funktioniert mit allen standardmäßigen Gettern und Settern, auch wenn die betreffenden Klassen in Java geschrieben sind. Sie können also auch "abc".bytes anstelle von "abc".getBytes() schreiben.

Die Property-Notation von Groovy ist besonders bequem, wenn Sie die Bean-Properties in Formeln benutzen. So haben etwa die beiden folgende Zeilen dieselbe Bedeutung:

p.punkte++
p.setPunkte(p.getPunkte()+1)

Typisierung

Groovy arbeitet grundsätzlich nur mit Objekten. Primitive Datentypen können Sie zwar als Klassenmember oder in Arrays verwenden; sobald Sie aber irgend etwas damit machen, wird es erst einmal in ein Objekt umgewandelt. Daher ist es beispielsweise möglich, in Groovy an einem Zahlenwert eine Methode aufzurufen, wie etwa 42.dump().

So ist in Groovy alles ein Objekt, dessen Typ muss aber nicht unbedingt bekannt sein. Wenn Sie beispielsweise eine Variable c als Collection deklarieren, können Sie ihr – genau wie in Java – eine Instanz der Klasse ArrayList zuweisen. Anders als in Java können Sie dann aber problemlos c.trimToSize() aufrufen, ohne irgendwelches Typecasting durchzuführen, obwohl diese Methode für Collection gar nicht definiert ist. Tatsächlich löst Groovy alle Methoden-, Feld- und Property-Aufrufe erst zur Laufzeit auf - wenn ein Member an einem Objekt definiert ist, kann es immer auch unmittelbar verwendet werden.

Konsequenterweise lässt man in Groovy daher die Typisierung von Variablen und Methoden häufig ganz weg. Anstelle der Typangabe können Sie bei deren Deklaration einfach des Schlüsselwort def verwenden, und als Methodenparameter brauchen Sie nicht einmal mehr das def angeben.

def s = "ein Text"
def l = s.size()
def n = l+1
def meineMethode (arg1, arg2, arg2) { . . . }

Closures

Groovy führt mit den Closures ein neues Syntax-Element ein, das der Sprache enorme zusätzliche Möglichkeiten verleiht. Eine Closure ist einfach ein anonymes Stück Programmcode, das in geschweifte Klammern eingeschlossen ist, und das einer Variablen zugewiesen oder als Argument eines Methodenaufrufs übergeben werden. Beispielsweise weist die folgende Programmzeile der Variablen c eine Closure zu, die das aktuelle Datum auf der Konsole ausgibt:

def c = { println (new Date()) }

Sie können die Closure aufrufen, indem Sie einfach an der Variablen, der sie zugewiesen ist, die Methode call() aufrufen. In den meisten Kontexten kann auch die Closure syntaktisch wie eine Methode behandelt werden. Beide folgenden Programmzeilen geben also das aktuelle Datum aus.

c.call()
c()

Closures können auch Parameter haben. Sie werden hinter der öffnenden geschweiften Klammer aufgeführt und durch die Zeichen -> vom nachfolgenden ausführbaren Teil der Closure getrennt. Wenn es nur einen Parameter gibt, so muss er nicht angegeben werden und bekommt dann den Namen it. Die folgende Closure addiert die beiden untypisierten Parameter a und b:

def d = { a,b -> return a+b }
println d(1,2)   // gibt den Wert 3 aus.

Sehr häufig werden Closures unmittelbar als Argument eines Methodenaufrufs definiert. Meist tritt die Closure als einziger oder als letzter Parameter der Methode auf; in diesem Fall kann die Runde Klammer um das an Ort und Stelle definierte Closure-Argument weggelassen werden. Die folgenden beiden Methodenaufrufe

meinFile.eachLine ({ println it })
meineDatenbank.eachRow ("select * from person", {
    println "Name:$name, Vorname: $vorname"
} )

können also etwas übersichtlicher so geschrieben werden:

meinFile.eachLine { println it }
meineDatenbank.eachRow ("select * from person") {
    println "Name:$name, Vorname: $vorname"
}

Closures „sehen“ immer den definierenden, nicht den ausführenden Kontext. Sie haben also Zugriff auf die Member der Klasse und die lokalen Variablen der Methode, innerhalb derer sie definiert sind und nicht innerhalb derer sie aufgerufen werden.

Sie können dies ändern, indem Sie der Closure-Property delegate ein Objekt zuweisen; dessen öffentliche Member werden dann für die Closure zusätzlich sichtbar. Das folgende Codestück definiert eine Closure, die auf eine (zunächst unbekannte) Property bytes zugreift. Erst durch Zuweisung eines String als Delegate bekommt sie einen Sinn, indem sie auf die getBytes()-Methode des String zugreift:

c = { println bytes }
c.delegate = "abc"
c()  // -> [97, 98, 99]

Erweiterte Sprachkonstrukte

Groovy unterstützt (bis auf wenige Ausnahmen) alle Sprachkonstrukte von Java und macht einige von ihnen wesentlich mächtiger. Als Beispiel nennen wir hier for-Schleifen und switch-Verteiler.

Groovy-for

Eine Groovy-eigene Variante der for-Schleife kann alle Elemente eines Objekts durchlaufen, für das die iterator()-Methode definiert ist.1) Dabei braucht die Laufvariable nicht definiert zu werden.

Groovy ordnet dem String eine vordefinierte iterator() Methode zu, die nacheinander alle einzelnen Zeichen liefert. Daher kann man mit folgender Schleife alle Zeichen durchlaufen:

for (c in "Hallo") { println c }

Groovy-switch

Das switch-Konstrukt sieht in Groovy syntaktisch genau so aus wie in Java, kann aber nicht nur wie in Java auf Ganzzahlen, char und Enums angewendet werden, sondern auf jeden beliebigen Typ.

Die case-Klauseln prüfen wie bei Java in der Regel auf Gleichheit mit dem switch-Wert; dabeit gibt es aber einige Ausnahmen:

  • Eine Liste oder ein Range prüft Enthaltensein.
  • Eine Klasse oder ein Interface prüft auf Implementierung oder Erweiterung des betreffenden Typs.
  • Eine Closure prüft, ob, ihr Ergebnis (Groovy-)true ist.
  • Ein regulärer Ausdruck prüft, ob der Wert mit ihm übereinstimmt.

Entscheidend ist, was die Methode isCase() des jeweiligen case-Ausdrucks liefert. Diese Methode ist von Groovy so vordefiniert, dass sie gleichbedeutend mit equals() ist, wird aber von einigen Typen entsprechend überschrieben.

Vordefinierte Methoden (GDK)

In Groovy können Sie an diversen Objekten verschiedene Methoden aufrufen, die in den jeweiligen Klassen weder definiert noch geerbt worden sind. Beispielsweise gibt es eine Methode String.iterator(), die einen Iterator über alle in einer String-Instanz enthaltenen Zeichen liefert. Wenn Sie in das JavaDoc der Java Standard Edition schauen, finden Sie dort keine solche Methode für die Klasse java.lang.String. Vielmehr wird die Methode mit Hilfe eines Mechanismus der Sprache definiert, der dazu dient, den Java-Standardklassen und -Interfaces zahlreiche zusätzliche Methoden zuzuordnen.

Solche Methoden bezeichnen wir als vordefinierte Methoden; die Menge aller dieser Methoden wird auch oft als Groovy Development Kit (GDK) bezeichnet. Sie treten nur dann in Erscheinung, wenn eine Klasse nicht selbst eine Methode mit der betreffenden Signatur definiert oder erbt. Überwiegend dienen diese zusätzlichen Methoden dazu, die Verwendung der jeweiligen Objekte in Zusammenhang mit Closures zu ermöglichen oder, wie wir weiter unten sehen, Operatoren zu überladen. Einige haben auch den Zweck, systematische Defizite in der Java-Standardbibliothek auszugleichen; so sorgt Groovy etwa dafür, dass bei allen Container-Objekten wie Arrays, Listen, Maps und Strings eine einheitliche Methode size() zur Verfügung steht, die jeweils die Anzahl der enthaltenen Elemente liefert.

Ein häufig verwendetes Beispiel für eine vordefinierte Methode ist println(), die wir weiter oben bereits mehrfach genutzt haben. Sie ist für den Typ java.lang.Object vordefiniert und kann daher innerhalb jeder beliebigen Klassen verwendet werden. Ihre Aufgabe besteht im Wesentlichen darin, das übergebene Argument an System.out.println() weiterzuleiten und damit Konsolenausgaben etwas zu erleichtern.

Ebenfalls sehr häufig werden vordefinierte Methoden für Schleifenkonstrukte verwendet, wie die folgenden Beispiele zeigen:

// Führt die Closure von 0 bis 6 aus:
7.times { println it }
// Führt die Closure von 1 bis 7 aus:
1.upto(7) { println it }
// Führt die Closure für jedes Listenelement aus:
liste.each { println it }
// Desgleichen für jedes Map-Element:
map.each { key,val -> println "$key=$val" }

Eine JavaDoc-ähnliche Dokumentation aller in der Groovy-Standardbibliothek vordefinierten Methoden finden Sie unter http://groovy.codehaus.org/groovy-jdk/.

Dynamisches Programmieren

Groovy erweitert nicht nur selbst bestehende Klassen um zusätzliche Methoden, sondern bietet auch den Entwicklern verschiedene Möglichkeiten, vorhandene Klassen dynamisch „aufzubessern“. Eine davon besteht in dem so genannten Metaobjekt-Protokoll (MOP), mit dessen Hilfe Sie auch selbst einzelnen Objekten oder ganzen Typhierarchien neue Methoden und Properties zuordnen können.

Die Möglichkeiten der dynamischen Programmierung werden von Grails intensiv genutzt und heben es damit von anderen Java-basierten Web-Entwicklungsplattformen ab. Beispielsweise können Sie mit dem folgenden Methodenaufruf alle in der Datenbank gespeicherten Person-Objekte abrufen:

List allePeronen = Person.list()

Eine statische Methode list() haben Sie nie definiert, von einer übergeordnete Klasse geerbt worden kann sie sein und auch eine derartige vordefinierte Methode gibt es nicht. Die Methode list() ist einfach von Grails hinzugefügt worden, um Ihnen eine Routine-Arbeit abzunehmen, die der Computer ebenso gut erledigen kann.

Wir wollen an dieser Stelle nicht näher auf diese Mechanismen eingehen, da sie den Rahmen dieser Kurzeinführung sprengen würden. Das folgende Beispiel soll Ihnen aber zeigen, wie einfach Sie beispielsweise der Java-Klasse String eine neue Methode hinzufügen können, die alle Leerzeichen am Ende des Strings abschneidet.

String.metaClass.trimRight = { -> 
   delegate.replaceFirst(/\s+$/,'')
}
println "abc  ".trimRight() ---> "abc"

Builder

Das Metaobjekt-Protokoll von Groovy erlaubt sogar, noch einen Schritt weiter zu gehen und die Semantik eines Methoden- oder Property-Aufrufs neu zu bestimmen. Diese Fähigkeit ist die Grundlage für die Definition so genannter domainspezifischer Sprachen, die sich in ihrer Form an eine bestimmte fachliche Aufgabenstellung anpassen. Groovy selbst nutzt diese Möglichkeit in verschiedenen Hilfsklassen, mit denen man unterschiedliche Arten von hierarchischen Strukturen aufbauen kann. Ein Beispiel dafür ist der SwingBuilder, mit dem man in übersichtlicher Form Swing-Oberflächen zusammenstellen kann. Ein anderes Beispiel ist der MarkupBuilder zum Aufbau von XML- und HTML-Dokumenten.

Das folgende Beispiel zeigt, wie man in Groovy ein HTML-Dokument generieren kann.

def sw = new StringWriter()
def mb = new groovy.xml.MarkupBuilder (sw)
mb.html {
    head { title "Ein generiertes Dokument" }
    body {
        h1 (class:'beispiel',"Das generierte Dokument")
        ol (type:'A') {
            ["eins","zwei","drei"].each {
                li "Nummer ${it}"
    }   }   }   }
println sw.buffer

Das Skript gibt bei seiner Ausführung den folgenden HTML-Code aus:

<html>
  <head>
    <title>Ein generiertes Dokument</title>
  </head>
  <body>
    <h1 align:"center">Das generierte Dokument</h1>
    <ol type="I">
      <li>Nummer eins</li>
      <li>Nummer zwei</li>
      <li>Nummer drei</li>
    </ol>
  </body>
</html>

Das Prinzip lässt sich leicht erkennen: Jeder Methodenname definiert ein HTML-Element, die benannten Parameter (name:wert) werden zu Element-Attributen und jedes Closure-Argument bildet eine neue Hierarchieebene im HTML-Dokument.

Operatoren

In Groovy können Sie viel häufiger Operatoren anwenden, wo Sie bei Java umständliche Methodenaufrufe schreiben müssen. Mit wenigen Ausnahmen werden allerdings nur die von Java bekannten Operatoren auf weitere Typen angewendet. So können Sie beispielsweise die üblichen arithmetischen Operatoren auch mit BigInteger- und BigDecimal-Werten benutzen.

Operatoren überladen

Die meisten Operatoren sind mit Hilfe von Methoden implementiert. So wird etwa das Plus-Zeichen in einen Aufruf der Methode plus() beim links stehenden Operanden übersetzt; a+b ist also dasselbe wie a.plus(b). Das bedeutet, dass Sie diverse Operatoren ganz einfach überladen können –- Sie müssen nur die entsprechenden Methoden an Ihren Klassen implementieren, und schon lassen auch sie sich über Operatoren verknüpfen. Und damit dies auch bei den Klassen der Java-Standardbibliotheken funktioniert, gibt es für alle Operatoren vordefinierte Methoden. Ein Beispiel dafür ist die Java-Klasse java.lang.String, die eigentlich keine plus()-Methode hat. Es gibt aber in der Groovy-Umgebung eine gleichnamige vordefinierte Methode für den String-Typ, und damit funktionieren die beiden folgenden Ausdrücke gleichermaßen:

"abc"+"def"
"abc".plus("def")

Und auch Sie können dafür Sorgen, dass das Pluszeichen auf eine von Ihnen programmierte Klasse anwendbar ist - Sie müssen nur die dem Operator zugeordnet Methode implementieren:

class MeinObjekt {
  MeinObjekt plus (MeinObjekt param) { . . . }
}

Eine Übersicht der Operatoren und der jeweils zugeordneten Methoden finden Sie auf der Groovy-Website unter http://groovy.codehaus.org/Operator+Overloading.

Neue Operatoren

Im Wesentlichen hält sich Groovy an den durch Java definierten Bestand an Operatoren - verleiht ihnen aber per Überladen in vielen Fällen eine veränderte Bedeutung. Einige neue Operatoren sind aber auch noch hinzu gekommen. Wir wollen sie hier kurz erläutern.

Der Spread-Operator (*). Wenn Sie vor einem Methodenargument, das ein Array oder eine Liste ist, einen Stern setzen, wird nicht das Array oder die Liste, sondern es werden die darin enthaltenen Elemente als einzelne Argumente an die betreffende Methode übergeben.2)

def argumente = ['i','u']
"Feier".replace(*argumente) // ist dasselbe wie...
"Feier".replace('i','u')

Der Spread-Dot-Operator (*.). Führt eine Methode für alle Elemente einer Liste oder bildet eine Liste von Properties von Listenelementen.

 

Die sichere Dereferenzierung (?.). Erspart uns die Prüfung, ob ein Objekt nicht null ist, wenn wir es dereferenzieren wollen. Folgendes Beispiel führt nicht zu einer NullPointerException, wenn text den Wert null hat, sondern liefert einfach null.

x = text?.length()

Der 'Elvis'-Operator (?:). Ist eine Kurzform des ternären Operators (mit nur noch zwei Operanden). Liefert den Wert des ersten Operanden, wenn dieser true im Groovy-Sinne ist, und andernfalls den Wert des zweiten Operanden.

x ?: y // ist etwa gleichbedeutend mit
x ? x : y

Dabei wird der Ausdruck x aber kein zweites Mal ausgewertet, es ist also kein Problem, wenn die Auswertung von x sehr aufwändig ist oder zu Nebenwirkungen führt.

Erweiterte Typunterstützung

In Groovy werden die wichtigsten Standardtypen viel besser unterstützt als in Java. Wir wollen sie hier kurz vorstellen.

Listen

Listen können Sie auf einfache Weise erzeugen, indem Sie einfach die enthaltenen Elemente aufzählen und in eckige Klammern einschließen. Beispielsweise definiert [1,2,3] eine Liste aus drei Zahlen, und [] eine leere Liste. Beide Listen sind vom Typ java.util.ArrayList.

Auf die Elemente einer Liste (also eines Objekts, das dessen Klasse java.util.List implementiert) können Sie zugreifen wie auf die Elemente eines Arrays. Dabei können Sie auch negative Indexwerte, mehrere Indexwerte und Wertebereiche angeben.

def worte = ["alpha","beta","gamma"]
println worte[1]      // gibt "beta" aus
worte[3] = "delta"    // fügt ein neues Element hinzu
println worte[-1]     // liefert den letzen Wert, also "delta"
println worte[1,3]    // liefert eine neue Liste mit den Werten 1 und 2.
println worte[1..3]   // liefert eine neue Liste mit den Werten 1, 2, 3.

Wertebereiche (Ranges)

Der Wertebereich ist ein von Groovy definierter, unveränderlicher Standardtyp, der eine Folge von Werten repräsentiert, die durch die eine Ober- und eine Untergrenze definiert ist. Er kann rechts-inklusiv (untergrenze..obergrenze) und rechts-exklusiv (untergrenze..<obergrenze) definiert werden. Ein paar Beispiele dazu:

1..3      // 1, 2, 3
1..<3     // 1, 2
1.5..5    // 1.5, 2.5, 3.5, 4.5
2..-2     // 2, 1, 0, -1, -2
'x'..'y'  // 'x','y','z'
new Date(108,11,24) .. new Date(107,11,26) // 24., 25., 26.12.2007

Alle Wertebereiche implementieren das Interface groovy.lang.Range, das wiederum java.util.List erweitert. Somit sind alle Standardmethoden und Operatoren einer immutablen Liste anwendbar.

Maps

Ähnlich wie Listen können auch Maps direkt definiert werden. In diesem Fall müssen Schlüssel und Werte jeweils über einen Doppelpunkt verknüpft werden. Wenn der Schlüssel ein String der Form eines gültigen Namens ist, können die Anführungszeichen weggelassen werden. Auf diese Weise definierte Maps sind vom Typ java.util.LinkedHashMap, bewahren also die Anordnung der in ihnen gespeicherten Elemente.

Auch auf Map-Elemente kann wie auf die Elemente eines Arrays zugegriffen werden, allerdings ist an der Stelle des Index natürlich ein Werte vom Typ des jeweiligen Schlüssels anzugeben. Auch hier ein paar Beispiele:

def person1 = [name:"Klaus Müller", geburtsdatum:new Date(80,10,20)]
println person1["name"]
person1["email"] = "klaus.mueller@beispiel.de"

Neben der Array-Notation erlaubt Groovy auch den Zugriff auf Map-Elemente analog zu Properties. Die letzten beiden Zeilen des Beispiels könnten also auch so geschrieben werden:

println person1.name
person1.email = "klaus.mueller@beispiel.de"

In dieser Form geht natürlich nur, wenn die Schlüssel Strings sind und den Namensregeln entsprechen. Allerdings können bei Groovy die Property-Namen auch in Anführungszeichen eingeschlossen werden, so dass auch andere String-Schlüsselwerte adressierbar sind:

person."plz-ort" = "10178 Berlin"

Strings

Strings sind in Groovy interpolierbar, das heißt Sie können Werte und beliebig komplexe Ausdrücke einfügen, indem Sie diesen ein Dollarzeichen voranstellen. Besteht ein eingebetteter Ausdruck aus mehr als einem oder mehreren, mit Punkten verknüpften Namen, so muss er außerdem in geschweifte Klammern gesetzt werden. Sie können das Interpolieren unterdrücken, indem sie einfache Hochkommas anstelle von Anführungszeichen verwenden oder indem Sie einen Backslash vor das Dollarzeichen setzen.

println ("Die Person heißt $person.name.")
println ("Die aktuelle Zeit ist ${new Date()}")
println ('Hier bleibt das $-Zeichen erhalten.')
println ("Hier bleibt das \$-Zeichen auch erhalten.')

Sobald ein String interpoliert ist, hat er nicht mehr den Typ java.lang.String sondern groovy.lang.GString. Strings und GStrings verhalten sich weitgehend identisch; letztere bieten allerdings einige zusätzliche Möglichkeiten, auf die wir hier aber nicht weiter eingehen wollen.

Strings können anstelle von Anführungszeichen () oder Hochkommas (')auch in Schrägstrichen (/) eingeschlossen sein. Sie unterscheiden sich nur insofern von normalen Strings, als der Backslash (\) nicht als Escape-Zeichen erkannt wird (sofern es nicht vor einem Schrägstrich oder einem „u“ steht). Dies erleichtert die Angabe von regulären Ausdrücken erheblich, wie folgendes Beispiel zeigt:

pattern = ~/(\d\d)\.(\d\d)\.(\d\d\d\d)/

Reguläre Ausdrücke werden durch Groovy in mehrfacher Weise direkt unterstützt. Einerseits dient der unäre Tilde-Operator dazu, wie hier gezeigt einen regulären Ausdruck aus einem String zu bilden. Daneben gibt es einen einen Operator ==~ für Mustervergleiche zwischen zwei Strings:

if (kandidat ==~ muster) { . . . }

Ein weiterer Operator =~ erzeugt einen Matcher, der in Groovy wie eine Liste von Treffern behandelt werden kann:

("Groovy und Grails" =~ /\w+/).each { println it }

Zahlen

Im Unterschied zu Java verwendet Groovy für standardmäßig für gebrochene und sehr große numerische Literale die Typen java.math.BigDecimal und java.math.BigInteger. Das hat den Vorteil, dass die vor allem in kommerziellen Anwendungen lästigen Rundungsprobleme der binären Fließkommazahlen entfallen. Trotzdem können Sie mit diesen Zahlen die üblichen arithmetischen Operationen durchführen.

1 / 3 // ergibt den BigDecimal-Wert 0.3333333333.

Sie können aber auch in Groovy mit Float und Double arbeiten; es müssen die Werte nur entsprechend deklariert sein.

1 / 3d  // ergibt den Double-Wert 0.3333333333333333

Soweit unsere kleine Übersicht zur Programmiersprache Groovy. Für Hinweise auf Fehler oder mögliche Verbesserungen sind wir dankbar.

1) Die betreffende Klasse braucht nicht wie bei Java 5.0 als Iterable deklariert zu sein.
2) In bestimmten Fällen - wie in diesem Beispiel - können Sie das Sternchen sogar weglassen, weil Groovy auch so erkennt, dass die Liste die beiden einzelnen Argumente enthält.

Diskussion

ADQPB
groovy/groovy_fuer_grails-entwickler.txt · Zuletzt geändert: 15.11.2008 von jst
Recent changes RSS feed Creative Commons License Donate Driven by DokuWiki