Für die Definition von Methoden gelten ähnliche Regeln wie für die Definition von Variablen. Auch hier kann der Typ weggelassen werden, und wenn weder ein Typ noch irgendwelche Typmodifikatoren noch void angegeben sind, muss wenigstens das Schlüsselwort def die Definition kennzeichnen. Das ist auch häufig der Fall, denn im Unterschied zu Java sind Methoden nicht auf Package-Sichtbarkeit begrenzt, sondern öffentlich, wenn die Sichtbarkeit nicht explizit festgelegt ist.

Rückgabewerte

Bearbeiten

Jede Methode, die nicht als void typisiert ist, kann einen Wert zurückgeben. Im Unterschied zu Java muss aber ein Rückgabewert nicht explizit angegeben werden. Endet der Methodendurchlauf nicht mit return und einem benannten Rückgabewert, dient einfach das Ergebnis der letzten ausgeführten Anweisung als Ergebnis. Im einfachsten Fall schreiben Sie einfach das Ergebnis ohne return an das Ende der Methode.

In unserer obigen KalenderDatum-Klasse haben wir schon stillschweigend davon Gebrauch gemacht:

def get(String propertyname) {
  calendar.get(calendarConst(propertyname))
}
def getMonth() {
  calendar.get(Calendar.MONTH) + 1
}

Eine return-Anweisung ist in beiden Methoden nicht nötig, da ohnehin das Ergebnis der letzten Anweisung zurückgegeben wird, und das ist hier jeweils das Ergebnis des Aufrufs von calendar.get(). Wenn die letzte Anweisung kein Ergebnis hat, z.B. weil darin eine void-Methode aufgerufen worden ist, oder wenn überhaupt keine Anweisung ausgeführt worden ist, ist das Ergebnis null. Wichtig ist, dass hier mit „letzter Anweisung“ tatsächlich die Anweisung gemeint ist, die unmittelbar vor der schließenden geschweiften Klammer der Methode steht. Wenn diese aufgrund von Verzweigungen, oder weil eine return-Anweisung ohne Argument durchlaufen wurde, nicht erreicht wird, ist das Ergebnis null.

Wir haben hier eine einfache Methode, die eine Wettervorhersage nach einem Pseudo-Zufallsverfahren als Text generieren soll (sie wird definitiv nicht vom Wetterdienst verwendet):

def wettervorhersage() {
  if (System.currentTimeMillis() % 3) {
    "Es wird regnen."
  } else {
    "Es wird die Sonne scheinen"
  }
}

Die Methode liefert immer null, da eine letzte Anweisung vor der schließenden Klammer nicht existiert und daher auch nie erreicht werden kann. (Also gar kein Wetter, in manchen Zeiten durchaus eine brauchbare Prognose, aber nicht das, was wir wollten.) Wir müssen also auf das gute alte return zurückgreifen:

def wettervorhersage() {
  if (System.currentTimeMillis() % 3) {
    return "Es wird regnen."
  } else {
    return "Es wird die Sonne scheinen"
  }
}
Aktuell: In neueren Groovy-Versionen funktioniert auch die erste Variante dieses Beispiels korrekt, da auch in if- und anderen Verzweigungen der Wert des letzten Ausdrucks als Rückgabewert verwendet wird.

Unabhängig vom Ergebnis der letzten Anweisung ist der Rückgabewert einer Methode auch dann immer null, wenn sie explizit als void deklariert worden ist. Insofern ist die Bedeutung des Schlüsselwortes void auch etwas anders als in Java: Groovy erlaubt Ihnen, jede Methode aufzurufen, auch eine als void deklarierte – allerdings erhalten Sie als Ergebnis dann immer nur null.

Das Einsparen der return-Anweisung ist nicht unproblematisch. Wenn Sie in Java in einer Methode mit Rückgabewert vergessen, eine return-Anweisung mit passendem Argument explizit anzugeben, meldet schon der Compiler einen Fehler. In Groovy merken Sie das unter Umständen überhaupt nicht, da die Methode immer irgendetwas, und sei es null, zurückgibt. Das Problem ist insbesondere bei längeren, tief verzweigten Methoden gravierend, weil Sie die Ausstiegspunkte Ihrer Methode unter Umständen nur noch schwer überblicken können.

An einem einfachen Beispiel lässt sich das Problem demonstrieren. Nehmen wir an, wir bauen eine Methode, die uns sagt, ob ein KalenderDatum vor oder nach Weihnachten liegt.

def pruefeWeihnachtszeit (KalenderDatum datum) {
  if (datum.month<12 || datum.dayOfMonth<24) {
    return "Es ist Vorweihnachtszeit"
  }
  if (datum.month==12 && datum.dayOfMonth>24) {
    return "Es ist Nachweihnachtszeit"
  }
}

Sie liefert Ihnen immer einen Text, der Ihnen sagt, ob Sie schon an die Beschaffung der Geschenke denken müssen oder noch nicht. Ausgerechnet am 24.12. versagt sie aber, weil der Programmierer an diesen Fall nicht gedacht hat, und der Compiler ihn nicht gewarnt hat. Die Methode liefert null, Ihr Programm stürzt ab – schöne Bescherung!

Aufrufparameter

Bearbeiten

Kennen Sie auch jene Klassen, die n-fach überladene Methoden mit jeweils bis zu m Parametern haben. Wenn Sie sie benutzen wollen, müssen Sie mühsam herausfinden, welche Variante Sie brauchen und in welcher Reihenfolge welche Argumente anzugeben sind. Eigentlich sollen die vielen Varianten dem Benutzer der Methode ermöglichen, bestimmte Argumente wegzulassen, die dann durch Vorgabewerte ersetzt werden. Groovy erleichtert Ihnen hier die Arbeit sowohl beim Programmieren der Methoden als auch beim Aufrufen durch ein paar kleinere syntaktische Leckerbissen.

Warnung: Es empfiehlt sich keinesfalls, die drei folgenden Möglichkeiten der Aufbesserung von Parameterlisten durch gleichlautende Methodennamen zu mischen. Wenn Sie beispielsweise eine Methode, die Vorgabewerte hat, mit einer anderen überladen, die benannte Parameter hat, ist nur noch schwer zu sagen, welche beim konkreten Methodenaufruf zum Zuge kommt.

Parameter mit Vorgabewerten

Bearbeiten

In Groovy können Sie die Parameter von Methoden mit einem Default-Wert belegen. Wenn die Parameter mit Vorgabewert beim Aufruf der Methode nicht belegt werden, erhält die Methode jeweils den Vorgabewert übergeben. Ein Beispiel:

def vorgabeMethode (Integer param1, param2, param3="Vorgabe", Long param4=null) {
  ...
}

Alle Parameter ohne Vorgabewert müssen, sofern es welche gibt, vor den Parametern mit Vorgabewert angeordnet sein. Beim Aufruf dieser Methode müssen Sie die param1 und param2 angeben, die Parameter param3 und/oder param4 können Sie weglassen, wenn Sie mit den Vorgabewerten einverstanden sind. Die Reihenfolge muss eingehalten werden, das heißt, Sie können param3 nur weglassen, wenn Sie auch param4 weglassen.

Die Virtuelle Maschine von Java kennt so etwas nicht. Daher muss sich der Groovy-Compiler bemühen und die notwendigen überladenen Methoden selbst erzeugen. Das Ergebnis sieht ziemlich genau so aus, als hätten Sie Folgendes programmiert:

def vorgabeMethode (Integer param1, param2) {
  vorgabeMethode (param1, param2, "Vorgabe", null)
}
def vorgabeMethode (Integer param1, param2, param3) {
  vorgabeMethode (param1, param2, param3, null)
}
def vorgabeMethode (Integer param1, param2, param3, Long param4) {
  ...
}

Achten Sie darauf, dass Sie nicht selbst eine Methode mit derselben Signatur schreiben wie eine der generierten Methoden. Der Compiler ignoriert diesen Konflikt stillschweigend und verwendet dann nur die von Ihnen geschriebene Methode.

Variable Parameterlisten

Bearbeiten

Die seit Java 5.0 verfügbaren variablen Parameterlisten gibt es in Groovy schon länger, allerdings funktionieren sie geringfügig anders und erfordern keine spezielle Syntax wie die drei Punkte in Java. In Groovy genügt es, den letzten Parameter der Methode als Array zu deklarieren. Wenn dann beim Aufruf der Methode das letzte Argument fehlt oder wenn das Argument an derselben Position und ggf. alle noch folgenden Argumente vom Typ des Array-Elements sind, wird der Methode ein Array mit dem letzten und allen noch folgenden Argumenten übergeben. Die Methode erhält also immer ein solches Array, es kann aber auch leer sein. Ein Beispiel sagt mehr als tausend Worte:

def variableMethode (String fix, Integer[] variabel) {
 println "$fix - $variabel"
}

Wenn wir die Methode interaktiv ausprobieren, erhalten wir folgende Ergebnisse:

groovy> variableMethode ("Kein variables Argument")
groovy> variableMethode ("Ein variables Argument",1)
groovy> variableMethode ("Drei variable Argumente",1,2,3)
groovy> variableMethode ("Variables Argument ist null",null)
Kein variables Argument - {}
Ein variables Argument - {1}
Drei variable Argumente - {1, 2, 3}
Variables Argument ist null – null

Am vierten Aufruf können Sie die Ausnahme von der Regel erkennen: wenn an der Stelle der variablen Argumentliste nur null übergeben wird, bekommen Sie auch diese null anstelle des Arrays. Wenn Sie auf Nummer sicher gehen wollen, sollten Sie also den variablen Parameter mit einem assert abprüfen.

„Benannte“ Parameter

Bearbeiten

Groovy kennt auch so etwas wie benannte Parameter, die sich schon in vielen Programmiersprachen – außer in Java – bewähren durften. Bei benannten Parametern ist die Reihenfolge der angegebenen Argumente egal, es muss aber jeweils der Name des Parameters mit angegeben werden. Besonders bei langen Parameterlisten kann dies vorteilhaft sein, da man sich nicht die Reihenfolge der Parameter merken muss.

Leider kennt die Virtuelle Maschine keine benannten Parameter, und so musste man sich bei Groovy mit einem kleinen Trick behelfen, der den Eindruck erweckt, als würde man die Parameter benennen. In Wirklichkeit instanziiert man nur ein Map-Objekt.

def benannteMethode ( Map args) {
  println "args: $args"
}

Damit die Methode benannte Parameter akzeptiert, muss sie also mit einem Parameter vom Typ Map deklariert werden. Beim Aufruf geben Sie dann Argumente in der Form name:wert an. Das ist genau dieselbe Notation, die Groovy für die Map-Initialisierung verwendet (Mehr über die Verwendung von Maps in Groovy in {{:Groovy: Link | Wie Groovy das JDK erweitert).

groovy> benannteMethode ()
groovy> benannteMethode (arg1:"eins",arg2:"zwei")
args: null
args: ["arg1":"eins", "arg2":"zwei"]

Das Problem bei der Methodendeklaration mit einer Map ist, dass ein Anwender der Methode nicht ohne weiteres erkennen kann, was für Argumente überhaupt erwartet werden. Als Hilfe erweist sich, wenn man in den ersten Zeilen der Methode die fehlenden Parameter initialisiert und dabei gleich kundtut, was für Erwartungen man an den Aufrufenden der Methode stellt. Beachten Sie auch, dass Sie statt der Map nur eine null erhalten, wenn gar kein benannter Parameter angegeben ist.

def benannteMethode ( Map args) {
  if (args==null) args = [:]
  def arg1 = args.get('arg1',"Erstes Argument")
  def arg2 = args.get('arg2',"Zweites Argument")
  //...
}

In diesem Beispiel werden einfach die Map-Elemente in lokale Variablen überführt. Die dabei verwendete get()-Methode ist eine für das Interface Map vordefinierte Methode, die das benannte Element aus der Map liest und diesen Wert oder, sofern er nicht vorhanden ist, einen Vorgabewert zurückgibt.

Aktuell: Seit der "Erfindung" des Elvis-Operators in Groovy würde man statt:
def arg1 = args.get('arg1',"Erstes Argument")

einfach diese Formulierung wählen:

def arg1 = args.arg1 ?: "Erstes Argument"

Sie können übrigens auch benannte Parameter und Positionsparameter in einer einzigen Methode haben. Die Positionsparameter werden dann am Ende der Argumentliste übergeben und müssen natürlich genau mit der definierten Parameterzahl übereinstimmen.

Methodenaufruf mit dem Spread-Operator

Bearbeiten

Groovy bietet durch den Spread-Operator die Möglichkeit, beim Aufruf von Methoden gleich mehrere Parameter zusammen zu übergeben, die in einem List-Objekt oder einem Array zusammengefasst sind.

Angenommen, Sie wollten in einem XML-Dokument einen Zeitabstand in einer standardkonformen Weise eintragen. Dabei haben Sie den Abstand aus einem anderen Anwendungsteil in der Form einer Liste, die die Anzahl Tage, Stunden, Minuten und Sekunden als einzelne Zahlen enthält. Die Methode newDurationDayTime() der Klasse DatatypeFactory akzeptiert – neben einem Kennzeichen dafür, ob es sich um eine positive oder negative Differenz handelt – diese Werte als einzelne Argumente. Statt beim Methodenaufruf die Werte aus der Liste einzeln anzugeben, können sie einfach die ganze Liste übergeben. Wir können dies ganz einfach mit groovysh demonstrieren.

groovy> dtf = javax.xml.datatype.DatatypeFactory.newInstance()
groovy> zeitabstand = [0, 3,45,10]
groovy> dur = dtf.newDurationDayTime(true, *zeitabstand)
groovy> println dur
P0DT3H45M10S

Die Liste zeitabstand enthält eine Liste mit vier Zahlen, die eine zeitlichen Abstand von 0 Tagen, 3 Stunden, 45 Minuten und 10 Sekunden repräsentieren. Im Methodenaufruf newDurationDayTime() übergeben wir diese Liste anstelle der korrespondierenden Einzelwerte und stellen ein Sternchen voran – den Spread-Operator. Dies führt dazu, dass die Liste beim Aufruf der Methode aufgelöst und durch ihre Elemente ersetzt wird. Der Spread-Operator lässt sich auf Listen und Arrays anwenden und funktioniert innerhalb der Argumentlisten beim Aufruf von Methoden, Konstruktoren und Closures.