Groovy: Was Sie einfach weglassen können

Aus Wikibooks

Es gibt einige Sprachelemente, die Sie von Java her gewohnt sind, die Sie aber in Groovy-Programmen einfach weglassen können, wenn Sie wollen, ohne dass sich der Compiler beschwert und ohne dass dies zu einer inhaltlichen Veränderung des Programms führt.

In welchem Umfang Sie von dieser Möglichkeit Gebrauch machen möchten, bleibt am Ende Ihnen überlassen. Manche sehen darin eine Möglichkeit, den Code übersichtlicher (weil schlanker) zu gestalten, andere halten lieber an der gewohnten Syntax fest. Für beides gibt es Argumente wie Vertrautheit, Schnelligkeit beim Schreiben oder Lesbarkeit, die – je nach Geschmack – auf beide Schreibweisen anwendbar sind.

Wir werden in diesem Buch die Vereinfachungsmöglichkeiten überwiegend nutzen, ohne dabei zu übertreiben, damit Sie sich mit der neuen Optik vertraut machen können.

Neue Konventionen gesucht

Wie bei Java so werden sich auch bezüglich Groovy im Laufe der Zeit Konventionen herausbilden, wie mit den Möglichkeiten der Sprache in einer einheitlichen Weise umgegangen wird. Für Groovy gilt dies insofern verstärkt, als die Sprache Ihnen häufig viele unterschiedliche Möglichkeiten bietet, ein Problem zu lösen.

In jedem Fall scheint es sinnvoll, sich Konventionen für die eigene Arbeit zu überlegen, und das gleiche Problem im Code nicht mal so und mal so zu lösen. Insbesondere gilt dies natürlich für Teams, die an einem gemeinsamen Projekt arbeiten. Genau so, wie es üblich ist, sich in Java-Projekten auf gemeinsame Kodierungsrichtlinien zu einigen, sollte man es auch in Groovy-Projekten tun. Wenn Sie das vorhaben, können Sie beispielsweise die üblichen Java-Richtlinien zur Grundlage nehmen und Groovy-spezifisch ergänzen.

Klassendefinition und Main-Methode[Bearbeiten]

Bei einfacheren, skriptartigen Programmen können Sie auf die explizite Definition einer Klasse verzichten und einfach die auszuführenden Anweisungen ohne main()-Methode der Reihe nach aufschreiben. Wir haben von dieser Möglichkeit schon im ersten Kapitel Gebrauch gemacht und werden sie im ganzen Buch für kurze Beispiele nutzen. Wenn Sie Ihre Programme durch Funktionen strukturieren möchten, schreiben Sie diese einfach an beliebiger Stelle dazu, sie haben dieselbe Form wie die Methoden von Klassen (und wir werden sie fortan, um nicht immer unterscheiden zu müssen, auch als „Methoden“ bezeichnen). Der Groovy-Compiler generiert aus dem Ganzen eine startbare Klasse mit dem Namen der Quelldatei (ohne Endung), deren main()-Methode das Skript aufruft.

Skriptcode und Klassen können in einer Datei beliebig gemischt sein; wenn allerdings eine Klasse denselben Namen hat wie die Datei, in der sie definiert ist, also etwa MeineKlasse in der Datei MeineKlasse.groovy, dann darf sich dort kein Skriptcode befinden, denn daraus müsste ja ebenfalls eine Klasse namens MeineKlasse generiert werden, aber zwei Klassen mit demselben Namen kann es in Java normal nicht geben.

Mehr zur Arbeit mit Groovy-Skripten finden Sie in Kapitel Objekte in Groovy.

Das Semikolon am Ende einer Anweisung[Bearbeiten]

In Java müssen Sie eine Anweisung immer mit einem Semikolon abschließen. In einem Groovy-Programm ist das in aller Regel nicht erforderlich. Das Semikolon wird nur dann benötigt, wenn sich mehrere Anweisungen in einer Zeile befinden. Statt

println ("Hello World");

können Sie also auch schreiben:

println ("Hello World")

Mit dieser kleinen Änderung verliert das Zeilenende gewissermaßen seine Unschuld, die es in allen Vorgängersprachen wie Pascal und C/C++ und auch in C# noch genießt. Ein Zeilenvorschub-Zeichen spielt in diesen wie bei Java genau dieselbe Rolle wie ein Leerzeichen oder ein Tab-Zeichen: Außerhalb eines Strings trennt es zwei Elemente der Sprache („Token“), ansonsten hat es keinerlei eigene Bedeutung. In Groovy dagegen ist das Zeilenvorschub-Zeichen etwas Besonderes: Es schließt eine Groovy-Anweisung ab, sofern dies an der gegebenen Stelle im Code syntaktisch zulässig ist.

Wenn Sie verhindern möchten, dass ein Zeilenende als Ende einer Anweisung erkannt wird, können Sie dies durch einen Backslash am Ende der Zeile deutlich machen.

summeDerDingsdaBumsda = anzahlDerDingens \
+ anzahlDerBummens

Das Backslash-Zeichen am Ende einer Zeile führt im jedem Fall dazu, dass die nächste Zeile vom Compiler logisch als Fortsetzung der aktuellen Zeile betrachtet wird.

Warnung. An das Weglassen der Semikolons an Zeilenenden gewöhnen Sie sich schnell. Dadurch handeln Sie sich jedoch leicht tückische Fehler ein, deren Ursache an dem resultierenden Fehlverhalten schwer zu erkennen ist. Wenn Sie beispielsweise im obigen Beispiel den Backslash vergessen,
summeDerDingsdaBumsda = anzahlDerDingens
+ anzahlDerBummens

haben Sie zwei gültige Anweisungen und bekommen keine Fehlermeldung, obwohl es sicher nicht Ihrer Erwartung entspricht, dass die Variable summeDerDingsdaBumsda am Ende nur den Wert von anzahlDerDingens hat und die zweite Zeile überhaupt nichts bewirkt. Gewöhnen Sie sich am besten daran, öffnende Klammern und binäre Operatoren immer an das Ende der ersten Zeile und nicht an den Anfang der zweiten Zeile zu schreiben, um dem Compiler klar zu machen, dass in der nächsten Zeile noch etwas kommt.

summeDerDingsdaBumsda = anzahlDerDingens +
  anzahlDerBummens

Die Klammern um Methodenparameter[Bearbeiten]

Beim Aufrufen von Methoden auf der obersten Ebene brauchen Sie die Argumente nicht unbedingt einklammern. Dies gilt also nur, wenn der Methodenaufruf nicht schon selbst Teile eines Ausdrucks ist. Statt

println ("Hello World")

können Sie also auch einfach dies schreiben:

println "Hello World"

Leere Argumentlisten müssen ihre Klammern behalten. In der folgenden Programmzeile, die nur einen Zeilenvorschub ausgibt, können Sie die Klammern also nicht weglassen.

println () // Hier dürfen die Klammern nicht weggelassen werden.

Auch nicht weglassen können Sie die Klammern um die Argumente in Konstruktor-Aufrufen.

new StringBuilder ("Anfangstext") // Hier auch nicht.

Dies wirkt etwas inkonsistent, ist es aber nicht, denn ein Konstruktoraufruf ist in der Regel Teil eines Ausdrucks, und da dürfen die Klammern auch bei Methodenaufrufen nicht fehlen.

Warnung. Auch hier kommt es leicht zu Fehlern, über die schon mancher Groovy-Programmierer lange gegrübelt hat. Sehen Sie sich diese Anweisung an:
println (a+b)/c

Eigentlich sollte klar sein, dass wir einen Wert aus drei Variablen errechnen und diesen ausgeben wollen. Es tritt aber eine Fehlermeldung auf, da Groovy annimmt, dass nur der Inhalt der Klammer (a+b) an println() übergeben werden soll und das Ergebnis des println()-Aufrufs durch c geteilt werden soll, was natürlich nicht geht, da es null ist.

Machen Sie von der Möglichkeit, die Klammern um Parameter wegzulassen, nur in ganz trivialen Situationen Gebrauch. Und setzen Sie die Klammern auf jeden Fall immer, wenn das erste zu übergebende Argument bereits mit einer Klammer beginnt.

Das Return am Ende einer Methode[Bearbeiten]

Wenn sich am Ende eines Methodenrumpfs kein explizites return befindet, wird das Ergebnis der letzten Anweisung als Methodenergebnis zurückgegeben. Daher bestehen Methoden in Groovy bisweilen nur aus einem einzeln stehenden Ausdruck:

Integer addiere (Integer a, Integer b) {
    a+b
}

Wir hätten natürlich auch return a+b schreiben können, aber der Ausdruck allein reicht völlig aus. Wenn die letzte Anweisung eine void-Methode aufruft und daher kein Ergebnis hat, wird folgerichtig null zurückgegeben. Hierzu ein Beispiel.

String protokolliere (String text) {
    println("Protokoll "+new Date()+": "+text)
}

Die Methode protokolliere() liefert unabhängig davon, dass sie mit einem String-Ergebnis deklariert ist, immer das Ergebnis null, denn die Methode println() in der einzigen und letzten Anweisung ist als void definiert.

Eine Methode, die selbst als void definiert ist, liefert immer null als Ergebnis und die Explizite Rückgabe eines Wertes ist nicht möglich. (Auch eine mit dem Rückgabetyp void definierte Methode kann in Groovy durchaus als Funktion aufgerufen werden).

groovy> void nullfunktion() { 42 }
groovy> println nullfunktion()
null

Die am häufigsten gebrauchten Imports[Bearbeiten]

In jedem Java-Programm ist das Package java.lang automatisch importiert, ohne dass Sie dies besonders erwähnen müssen. Damit stehen Ihnen unter anderem die String-Klasse, die Wrapper-Klassen zu den primitiven Typen wie Boolean und Integer sowie die grundlegenden Exception-Klassen zur Verfügung. Es sind genau diejenigen Klassen, die auch dem Compiler und der Laufzeitumgebung bekannt sein müssen, um überhaupt ein Java-Programm zu verstehen und ausführen zu können.

Groovy führt noch einige weitere Import-Anweisungen automatisch aus, deshalb können Sie die folgenden Imports in einem Groovy-Programm immer weglassen:

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

Das ist nicht nur für Sie bequemer (Sie wissen, dass etwa java.util.* am Anfang von fast jeder Java-Quellcode-Datei importiert wird), sondern auch deshalb notwendig, weil die Sprache Groovy wesentlich mehr Typen direkt unterstützt als Java; zum Beispiel gibt es spezielle Literale für java.util.HashMap und java.util.ArrayList. Daher müssen diese Klassen in jedem Programm bekannt sein.

Die zwangsweise Prüfung von Checked Exceptions[Bearbeiten]

Groovy unterscheidet nicht zwischen checked und unchecked Exceptions. Sie sind also nie gezwungen, Exceptions abzufangen, und demzufolge wird auch nicht die Throws-Klausel für Methodendeklarationen benötigt. Ob dies ein Vorteil ist, kann sicher diskutiert werden; fest steht aber, dass es eine nicht zu vernachlässigende Anzahl von Java-Experten gibt, die die Einführung der checked Exception in Java für einen Missgriff halten. Außerdem entspricht es eher der dynamischen Natur von Groovy, wenn Exceptions ebenso wie Variablentypen erst zur Laufzeit geprüft werden.

Auf jeden Fall wird der Code lesbarer, wenn ein großer Teil der Exception-Prüfungen, die ja häufig den Charakter von Pflichtübungen haben, entfallen. Und Sie finden in Groovy-Programmen kaum noch jene erstrangige Ursache für äußerst verzwickte Fehler: das vergessene Error-Handling.

// Java
try {
    ...
} catch (EineException ex) {}

Natürlich geht das auch in Groovy, da Sie aber nie gezwungen sind, Fehler zu prüfen, besteht auch nicht die Notwendigkeit, solche Provisorien zu schreiben, die zu beseitigen später allzu leicht vergessen wird.