Die Closure ist ein Sprachkonstrukt, das in ähnlicher Form – teils unter anderem Namen – auch schon in anderen Programmiersprachen Einzug gehalten hat und einen nachhaltigen Einfluss auf den gesamten Programmierstil ausübt. Tatsächlich stellen sich viele bekannten Patterns der Java-Programmierung ganz anders und oft viel einfacher dar, wenn man Closures zur Problemlösung heranziehen kann.

Tipp: Es deutet einiges darauf hin, dass Closures in absehbarer Zeit – gemeint ist Java 8 – auch Eingang in die Sprache Java finden werden. Wenn wir uns jetzt in Zusammenhang mit Groovy mit Closures beschäftigen, ist dies also auch gleich eine Vorbereitung auf das, was in Java auf uns zukommt.

Closures definieren

Bearbeiten

Man kann sich eine Closure quasi als ein Behälter für ein Stück Programmcode vorstellen, ähnlich einer Methode, der aber kein Member einer Klasse ist. Closures sind namenlos und können einer Variablen zugewiesen oder einer Methode als Argument übergeben werden. Das englische Wort closure kann am ehesten mit „Abschluss“ oder auch „Funktionsabschluss“ übersetzt werden. Damit ist gemeint, dass die in der Funktion enthaltenen „offenen“ Variablen, die auf Werte im umgebenden Scope verweisen, mit übernommen und damit gewissermaßen abgeschlossen werden. Da das deutsche Wort auch nicht viel aussagekräftiger ist und auch nicht sehr weit verbreitet ist, bleiben wir hier bei der englischen Fassung des Begriffs.

Einfache Closures

Bearbeiten

Die Closure beginnt und endet mit einer geschweiften Klammer, ähnlich einem Anweisungsblock, wie er überall im Code vorkommen kann. Die folgende Programmzeile definiert eine Closure und weist sie einer Variablen zu.

groovy> Closure c1 = { println 'Hello world!' }

Der Groovy-Compiler generiert intern aus dem Code-Stück zwischen den geschweiften Klammern eine namenlose Klasse als Erweiterung der abstrakten Klasse groovy.lang.Closure. Man kann das Code-Stück ausführen, indem man die die Methode call() eines Closure-Objekts aufruft.

groovy> c1.call()
Hello World!

Sie können es aber auch noch etwas einfacher haben und einfach die Variable, der die Closure zugewiesen worden ist, wie eine Methode behandeln.

groovy> c1()
Hello World!

Closures mit Parametern

Bearbeiten

Closures können auch Parameter haben. Die Parameter werden, durch Komma getrennt, nach der öffnenden geschweiften Klammer angeordnet und vom auszuführenden Rumpf durch ein Bindestrich und ein Größer-Zeichen (->) separiert. Die Parameter werden genau so deklariert wie Methodenparameter, können untypisiert sein und Vorgabewerte haben.

groovy> def c2 = { s1, s2 -> println s1 + ' ' + s2 +'!' }
groovy> c2('Hello','World')
Hello World!

Wenn die Closure nur einen Parameter hat, brauchen Sie ihn nicht zu benennen; er hat dann standardmäßig den Namen it. Wenn Sie eine solche parameterlose Closure ohne Argumente aufrufen, hat it den Wert null.

groovy> def c3 = { println 'Hello ' + it +'!' }
groovy> c3('World')
Hello World!
groovy> c3()
Hello null!

Sie können Closures genau wie Methoden auch mit Default-Werten für die Parameter versehen. Die folgende Abwandlung des obigen Beispiels c2 gibt „World“ aus, wenn der zweite Parameter nicht angegeben ist:

groovy> def c2a = { s1, s2='World' -> println s1 + ' ' + s2 +'!' }
groovy> c2a('Goodbye')
Goodbye World!

Außerdem kennen auch Closures variable Argumentlisten, bei denen Sie als letzten Parameter einfach ein Array verwenden:

groovy> def c4 = { Object[] args -> println "Args: "+args }
groovy> c4(100,'Alpha',new Date())
Args: {100, "Alpha", Mon Jul 16 18:16:07 CEST 2007

Mit Ausnahme der drei letztgenannten Fälle müssen Closures immer mit genau der deklarierten Anzahl von Argumenten aufgerufen werden, und wenn Parametertypen angegeben sind, müssen natürlich auch die Typen der Argumente zuweisungskompatibel sein.

Closures, die Methoden referenzieren (Multimethoden)

Bearbeiten

Für Closures, die nichts weiter machen, als an eine andere Methode zu delegieren, gibt es noch eine andere Notation. Angenommen, wir bräuchten eine Closure, die nur die Aufgabe hat, den übergebenen Parameter mit System.out.println() auszugeben. Sie sähe normalerweise so aus:

groovy> def c4 = { wert -> System.out.println wert }

Das können wir einfacher haben, indem wir eine Closure definieren, die keinen Rumpf enthält, sondern nur einen Verweis auf eine vorhandene Methode. Dazu dient eine spezielle Notation mit einem Punkt und einem Und-Zeichen (.&).

groovy> def c5 = System.out.&println
groovy> c5("Hallo Welt!")
Hallo Welt!

Unser Aufruf von c5() ist ohne weitere Umwege an die println()-Methode in System.out weitergeleitet worden. Vielleicht fällt Ihnen auf, dass c5 keinerlei Hinweis auf die Parameter der Methode enthält, es kann sich also nicht um eine vollständige Methodensignatur handeln. Und tatsächlich bestimmt Groovy wie vieles Andere die aufzurufende Methode erst zur Laufzeit anhand der aktuellen Argumente. Probieren Sie doch einmal:

groovy> c5(); println 'OK'

OK

Siehe da, es wird nur ein Zeilenvorschub ausgegeben, da mit c5() die überladene, argumentlose Methode System.out.println() aufgerufen worden ist. Dabei wird die zutreffende Methode erst zur Laufzeit anhand der Anzahl und des Typs der aktuell angegebenen Argumente bestimmt. Die Variable c5 repräsentiert also eigentlich den Zugriff auf verschiedene Methoden, und dies wird in Groovy als Multimethode bezeichnet.

Beachten Sie, dass in einer Methodenreferenz nur auf Instanzmethoden verwiesen werden kann, Referenzen auf statische Methoden sind nicht zulässig. Das obige Beispiel ist insofern korrekt, dass System.out eine vom System erzeugte Instanz der Klasse PrintStream ist.

Closures, die Closures referenzieren (Currying)

Bearbeiten

Closures sind Objekte und bieten daher verschiedene Methoden, darunter auch die Methode curry(). Sie erzeugt eine Variante der Closure, bei der ein oder mehrere Parameter vorbelegt sind. Erinnern Sie sich an die obige Closure c2.

groovy> def c2 = { s1, s2 -> println s1 + ' ' + s2 +'!' }

Mit Hilfe der Methode curry(), der wir ein Argument mitgeben bilden wir eine neue Closure, die nur noch ein Argument hat, nämlich s2, während s1 schon belegt ist.

groovy> def c2b = c2.curry('Hello')
groovy> c2b('World')
Hello World!

Das ist ungefähr äquivalent mit folgender Deklaration einer Closure mit einem Parameter, den sie als zweites Argument an c2 weiterreicht, während sie das erste Argument für c2 selbst liefert.

groovy> def c2c = { param -> c2.call('Hello',param) }

Das funktioniert übrigens auch mit Methodenreferenzen. Die folgende Curry-Closure referenziert die oben schon verwendete Closure c5, die ihrerseits die Methode System.out.println() referenziert.

groovy> def c5 = System.out.&println
groovy> def c5a = c5.curry('Hallo Universum!')
groovy> c5a()
Hallo Universum!
Tipp: Der Begriff curry hat nichts mit indischen Gewürzen zu tun sondern geht auf den amerikanischen Logiker und Mathematiker Haskell Brooks Curry zurück, der sich viel mit funktionaler Programmierung beschäftigt hat. Vor Mr. Curry soll bereits ein russisch-deutscher Mathematiker namens Moses Schönfinkel so etwas wie die curry()-Methode als logische Operation erfunden haben; daher wird dieser Vorgang auch gelegentlich als „schönfinkeln“ bezeichnet.

Rückgabewerte

Bearbeiten

Genau wie normale Methoden können auch Closures einen Wert zurückgeben. Wenn kein return explizit angegeben ist, wird das Ergebnis der innerhalb der Closure zuletzt ausgeführten Anweisung zurückgegeben. Hat die letzte Anweisung kein Ergebnis, z.B. weil es eine void-Methode ist, so ist das Ergebnis null.

Die folgende Closure berechnet rekursiv die Fakultät einer angegebenen Zahl.

groovy> def fakult
groovy> fakult = { val -> 
groovy> val>1 ? val*fakult(val-1) : 1 
groovy> }
groovy> println fakult(10)
3628800

Beachten Sie, dass bei die Variable fakult hier nicht erst in derselben Anweisung angelegt werden darf, in der auch die Closure definiert wird (also def fakult = {...}), da Sie aus der Closure heraus sonst nicht auf diese Variable zugreifen können.

Am Rande sei auch noch angemerkt, dass Sie den rekursiven Aufruf der Closure aus derselben Closure heraus nicht etwa mit this.call(val-1) oder this(val-1) bewerkstelligen können, denn die Closure hat, obwohl sie ein eigenes Objekt ist, gewissermaßen keine eigene Identität. Vielmehr verweist this auf die umgebende Klasse, hier also auf die Skript-Klasse.

Closures anwenden

Bearbeiten

Nachdem Sie erfahren haben, wie man Closures definiert, werden Sie vielleicht sagen: „Tolle Sache, aber was nützt mir das?“ Nun wird Ihnen dieses Syntax-Element fortan in diesem Buch in den verschiedensten Zusammenhängen begegnen, wo Sie selbst feststellen werden, wie einfach und übersichtlich manche Programmieraufgaben durch Closures gelöst werden können. Und am Ende können Sie sich vielleicht gar nicht mehr vorstellen, wie Sie noch ohne Closures programmieren können. Soweit ist es aber noch nicht, daher wollen wir Ihnen an dieser Stelle schon einmal die typischen Situationen vor Augen führen, wo Closures nutzbringend eingesetzt werden können.

Closures als Methodenparameter verwenden

Bearbeiten

Die obigen Beispiele sind insofern untypisch für die Anwendung von Closures, als man jedes Beispiel auch mit Hilfe einer Methode hätte implementieren können. Der ganz typische Anwendungsfall für Closures sind Methoden, deren Funktionalität eine Lücke aufweist, die durch die Closure geschlossen wird. Dies ermöglicht es, manche Klassen und Methoden sehr abstrakt zu formulieren, da das für eine bestimmte Situation Spezifische erst in der Anwendung in der Form einer Closure hinzugefügt werden kann.

Das vielleicht am häufigsten verwendete Beispiel dafür ist die Methode each(), die Groovy standardmäßig für alle möglichen Containerobjekte, darunter auch Listen, implementiert. Sie geht alle in dem Objekt enthaltenen Elemente durch und ruft für jedes dieser Elemente die in each() übergebene Closure auf. Wir simulieren dies im Beispiel einer von ArrayList abgeleiteten Beispielklasse mit einer Methode mitJedem(), die eine Closure als Argument annimmt.

class ListeMitClosure extends ArrayList {
  public mitJedem (Closure c) {
    for ( element in this) {
      c(element)
    }
  }
}

def liste = new ListeMitClosure();
liste.add("Eins")
liste.add("Zwei")
liste.add("Drei")

Closure ausgeben = {println it}
liste.mitJedem(ausgeben)

Wir übergeben dem Methodenaufruf von mitJedem() hier eine Closure namens ausgeben, die ihren Parameter it einfach auf der Konsole ausgibt, und tatsächlich erscheinen die drei Elemente der Liste nacheinander auf dem Bildschirm.

Eins
Zwei
Drei

Der Aufruf von mitJedem() ist noch zu umständlich. Natürlich können Sie die Closure auch direkt als Literal angeben.

liste.mitJedem({println it})

Und wenn die Closure an der letzten Stelle einer Argumentliste steht, kann sie auch aus dem Klammerpaar herausgezogen werden.

liste.mitJedem() {println it}

Und wenn dann zwischen den Klammern nichts mehr steht, können Sie diese auch gleich weglassen.

liste.mitJedem {println it}

Und damit haben Sie die Form, die auch in aller Regel benutzt wird, wenn die Closure das einzige Methodenargument ist.

Die Parametertypen einer Closure abfragen

Bearbeiten

Wie erwähnt, muss die Anzahl der definierten Parameter einer Closure immer mit der Anzahl der Argumente im Aufruf der Closure übereinstimmen, sofern die Parameter keinen Vorgabewert haben. Sie können aber beim Aufrufen der Closure die Anzahl und die Typen der definierten Parameter berücksichtigen und entsprechend reagieren. Für diesen Zweck hat die Closure eine Methode namens getParameterTypes(), die ein Class-Array mit den Typen aller definierten Parameter liefert.

groovy> def cl = {x,y -> println x+y }
groovy> println Arrays.toString(cl.getParamterTypes())
[class java.lang.Object, class java.lang.Object]

Wir möchten, dass in unserer Beispielklasse ListeMitClosure der Methode mitJedem() auch eine Closure mit zwei Parametern übergeben werden kann; in diesem Fall soll sie im zweiten Parameter den Index des gerade abgearbeiteten Elements erhalten. Dazu ergänzen wir die Methode mitJedem() um eine Abfrage der Parameter-Zahl.

class ListeMitClosure extends ArrayList {
public mitJedem (Closure c) {
  int index = 0
    for ( element in this) {
      if (c.getParameterTypes().length==2) {
        c(element, index++)
      } else {
        c(element)
} } } }

Nun können wir in der Listenausgabe auch den Index des jeweiligen Elements mit anzeigen.

liste.mitJedem { element, index -> println(index +'. '+element) }

Wobei der obige Aufruf mit nur einem Parameter immer noch funktioniert.

Closures zum Klassifizieren

Bearbeiten

Jede Closure hat eine Methode isCase() mit einem Parameter, die ihren Aufruf direkt an call() weiterleitet und das Ergebnis als Boolean zurückgibt. Dies qualifiziert Closures dazu, als case-Wert in switch-Anweisungen zu dienen. Sie erhalten dann das zu prüfende Objekt und entscheiden, ob der Fall gegeben ist oder nicht, indem sie true oder false (im Sinne der Groovy-Wahrheit) zurückgeben.

Mit dem folgenden Skript können Sie prüfen, ob der Verzehr von frischen Muscheln zur aktuellen Jahreszeit zu empfehlen ist.

switch (new Date()) {
case {new java.text.SimpleDateFormat('MMMM').format(it).contains('r')}:
  println "Muscheln essen erlaubt"
  break
default:
  println "Muscheln essen nicht erlaubt"
} 

Die Methode isCase() wird auch in einigen vordefinierten GDK-Methoden angewendet, zum Beispiel in grep():

groovy> def liste1 = [1,5,7,2,8,3]
groovy> def liste2 = liste1.grep { it % 2 }
groovy> println liste2
[1, 5, 7, 3]

In dem grep()-Aufruf werden alle Elemente in liste1, deren Modulo-2-Wert nicht 0 ergibt, in einer neuen Liste zusammengefasst und als Ergebnis zurückgegeben. Mehr über die Funktionsweise der vordefinierten Methode grep() erfahren Sie im Kapitel ##GDK##.

Closures als Event-Handler

Bearbeiten

Ein weiterer Anwendungsbereich, in dem Closures äußerst effizient eingesetzt werden können, ist das Event-Handling, wie es insbesondere bei der Oberflächenprogrammierung mit Swing Anwendung findet. Statt wie in Java spezielle Event-Handler zu programmieren, können Sie einfach dem Quellobjekt des Events eine Closure zuweisen. Folgendes Beispiel macht dies deutlich.

import javax.swing.*

def frame = new JFrame(title="Testfenster")
frame.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
def button = new JButton("Zum Beenden hier drücken!")
button.actionPerformed = { 
  if (! JOptionPane.showConfirmDialog(frame,"Wirklich beenden?")) {
    frame.dispose()
  }
}
frame.contentPane.add(button)
frame.pack()
frame.visible = true

Dieses kurze Skript öffnet ein Fenster, mit einem Button. Die Aktion, die beim Anklicken des Buttons ausgeführt werden soll, ist in einer Closure definiert, die einer Property actionPerformed des JButton zugewiesen wird. Was an dieser Stelle genau geschieht (denn der JButton hat gar keine Property actionPerformed), ist ein typisches Stück Groovy-Magie.

Closures als Threads

Bearbeiten

Closures implementieren von Hause aus das Interface Runnable und können somit auch asynchron ausgeführt werden. In dem folgenden Beispiel starten wir zwei Closures als parallel laufende Threads.

groovy> Thread.start { ('A'..'Z').each {sleep 100; println it} }
groovy> Thread.start { (1..26).each {sleep 100; println it} }
A
1
B
2

Jeder Thread schreibt eine Liste von Buchstaben bzw. Zahlen, wobei er bei jedem Durchlauf eine kurze Pause einlegt, damit jeweils der andere Thread zum Zug kommen kann – und damit wir das Ganze besser verfolgen können.

Mit Closures und Maps Interfaces implementieren und abstrakte Klassen erweitern

Bearbeiten

Eine Closure kann jedes beliebige Interface implementieren. Sie brauchen nur mit den Schlüsselwort as eine Typanpassung erzwingen, und schon werden alle Methodenaufrufe an Ihre Closure weitergeleitet. Sie können diese Möglichkeit dazu nutzen, jenen Typ von Interface zu implementieren, mit dem häufig eine Art Callback-Funktionalität hergestellt wird. In Java benutzt man in diesem Zusammenhang gewöhnlich innere anonyme Klassen.

Ein typisches Beispiel ist das Interface Comparator, das in Zusammenhang mit Comparable-Klassen benutzt wird und zur Abstraktion von Vergleichsmethoden für Sortierfunktionen dient, und dessen einzige hier relevante Methode compare() heißt. Das folgende Skriptbeispiel sortiert eine Liste mit String-Elementen ohne Beachtung der Groß- und Kleinschreibung:

groovy> liste = ["gamma","alpha","BETA"]
groovy> Collections.sort(liste, {x,y -> x.compareToIgnoreCase(y)} as Comparator)
groovy> println liste
["alpha", "BETA", "gamma"]

Der statischen sort()-Methode von Collections, die beliebige Listen sortiert, kann als zweites Argument ein Comparator für die Sortierung mitgegeben werden. Wir verwenden dazu einfach eine Closure mit zwei Argumenten, die einfach die String-Methode compareToIgnoreCase() aufruft und sich als Comparator verkleidet.

Sie können mit einer Closure natürlich auch ein Interface mit mehreren Methoden implementieren; der Closure werden einfach alle Methodenaufrufe übergeben. In diesem Fall empfiehlt es sich, als Parameter ein Objekt-Array (Object[]) zu verwenden, so dass eine beliebige Anzahl von Parametern beliebigen Typs übergeben werden kann. Mit einer solchen Closure kann man allerdings nicht allzu viel anfangen, da es keine Möglichkeit gibt, den Namen der aufgerufenen Methode zu ermitteln.

Wenn es gilt, ein Interface mit mehreren Methoden mit Closures zu implementieren, bietet sich noch eine andere Möglichkeit an. Legen Sie einfach eine Map an, der Sie für jede zu implementierende Methode eine Closure unter dem Methodennamen hinzufügen, und lassen Sie die Map mit as auf das Interface abbilden. Sie brauchen dabei nur Closures für diejenigen Methoden zuweisen, die auch tatsächlich benötigt werden.

Die folgende Programmzeile erzeugt einen Endlos-Iterator:

Iterator it = [hasNext:{true}, next:{1}] as Iterator

In diesem Beispiel implementieren wir nur die beiden Methoden hasNext() und next(), wobei die eine Methode immer true und die andere immer die Zahl 1 liefert. Die dritte Methode des Interface Iterator wird annahmegemäß nicht benötigt und bleibt daher unimplementiert. Solch Dummy-Implementierungen von Interfaces können sehr praktisch im Zusammenhang mit automatisierten Tests sein, um auf einfache Weise kontrollierte Bedingungen herzustellen. Dabei können die getesteten Programme durchaus normale Java-Programme sein, denn diese „sehen“ nur das implementierte Interface und brauchen ansonsten mit Groovy nichts weiter zu tun zu haben.

Auch die Ableitung von abstrakten Klassen mittels Closures oder Closure-Maps ist möglich. Dies können Sie folgendermaßen interaktiv ausprobieren:

groovy> def liste = [get:{int index->100},size:{1}] as AbstractList
groovy> println liste.get(1)
groovy> println liste.size()
100
1

Closures von innen

Bearbeiten

Sie werden, wenn Sie mit Groovy arbeiten, sehr viel mit Closures zu tun haben. Sie sind allgegenwärtig im GDK und den sonstigen Groovy-eigenen Bibliotheken, aber auch in Ihren eigenen Anwendungen werden Sie sehr schnell die Vorteile der kompakten Programmierung mit Closures schätzen lernen. Es ist daher nicht verkehrt, sich die Funktionsweise von Closures etwas genauer anzusehen.

Implementierung

Bearbeiten

Wenn Sie diese, Ihnen schon vom Anfang des Abschnitts bekante Closure definieren:

groovy> def c2 = { s1, s2 -> println s1 + ' ' + s2 +'!' }
groovy> c2('Hello','World')

Dann generiert der Groovy-Compiler daraus eine eigene namenlose Klasse, die die abstrakte Klasse groovy.lang.Closure erweitert und ihr eine Methode namens doCall() mit dem Rumpf der Closure hinzufügt. Als Java-Programm würden die beiden obigen Skriptzeilen sinngemäß ungefähr folgendermaßen aussehen:

// Java
import groovy.lang.Closure;

// Closure-Implementierung
class AnonymeClosure extends Closure {
  // Konstruktor
  public AnonymeClosure (Object owner) {
    super(owner);
  }

  // Closure-Methode
  void doCall(Object s1, Object s2) {
    System.out.println (s1+" "+s2+"!");
  }
}

// Main-Klasse zum Aufruf der Closure
public class ClosureTest {
  public static void main (String[] args) {
    new ClosureTest().runTest();
  }
  void runTest () {
    // Closure anlegen
    Closure c2 = new AnonymeClosure(this);
    // Closure ausführen
    c2.call(new String[]{"Hello","World"});
  }
}

Der eigentliche Rumpf der Closure befindet sich in der Methode doCall(). Sie überschreibt nicht, wie man erwarten könnte, eine in der Klasse groovy.lang.Closure definierte abstrakte Methode gleichen Namens, sondern wird aus call() dynamisch aufgerufen.

Worauf kann die Closure zugreifen?

Bearbeiten

Möglicherweise reichen einer Closure nicht die Informationen, die sie als Parameter übergeben erhält. In dem obigen Beispiel zum Event-Handling haben wir schon einen Closure gesehen, die auf eine Variable in ihrer Umgebung zugreift, in diesem Fall auf frame. Wir haben schon festgestellt, dass eine Closure zwar eine eigene Klasse ist, aber keine wirkliche eigene Identität hat. Innerhalb der Closure-Methode verweist this auf das Objekt, innerhalb dessen die Closure definiert worden ist, und der Sichtbarkeitsbereich umfasst demzufolge den Kontext der umfassenden Klasse. Sehen wir uns das an einem Beispiel an.

def zahl=42;
def c6 = {
  println this.class
  println owner.class
  println 'Die Zahl ist '+zahl
}

c6()
println c6.class

Nehmen wir an, wir speichern dieses Skript unter dem Namen TestClosure1.groovy und rufen es dann aus dem Befehlszeilenfenster auf. Dann sehen Sie auf dem Bildschirm folgendes.

> groovy TestClosure.groovy
class TestClosure1
class TestClosure1
Die Zahl ist 42
class TestClosure1$_run_closure1

Wir sehen, dass this.class auf die umgebende Klasse verweist, ebenso wie owner.class. Das Objekt, in dessen Kontext sich die Closure befindet, in diesem Fall also eine Instanz der Skript-Klasse TestClosure1, wird der Closure beim Instanziieren übergeben und ist dort als Property owner abrufbar. Und owner verweist auf das Objekt, an das alle Aufrufe weitergeleitet werden – bis auf ein paar Ausnahmen, darunter die Abfrage der Property owner.

Nur so ist es erklärlich, dass die obige Closure auf die außerhalb ihres Sichtbereichs definierte Variable zahl zugreifen kann, deren Wert sie in der dritten Zeile ausgibt. Dieser Mechanismus entspricht im Effekt weitgehend den inneren Klassen von Java, die es in Groovy als solche nicht gibt.

Der Aufruf von c6.class von außerhalb der Closure zeigt den wirklichen Klassennamen der Closure. Die Weiterleitung der Sichtbarkeit gilt also nur innerhalb der Closure-Methode; die Abfrage von c6.zahl würde daher einen Fehler auslösen, denn von außen gesehen hat die Closure keine Property mit dem Namen zahl.

Im Übrigen kann eine Closure auch auf lokale Variablen und Parameter der umgebenden Methode zugreifen. Diese werden in einem Kontext gesammelt und der Closure transparent übergeben.

Den Sichtbereich einer Closure modifizieren

Bearbeiten

Es gibt eine Möglichkeit, Einfluss darauf zu nehmen, worauf eine Closure zugreifen kann. Jede Closure hat zusätzlich zum Eigentümer-Objekt eine als Delegate bezeichnete Objektreferenz, an die Methoden- und Property-Aufrufe weitergereicht werden, sofern die Closure sie nicht selbst ausführen kann. Normalerweise ist delegate identisch mit owner, es gibt aber die Möglichkeit, ein anderes Objekt als delegate zu benennen. Dazu ein Beispiel:

groovy> def c7 = { println substring(12) }
groovy> c7.delegate = "Schöne neue Welt"
groovy> c7()
Welt

Die Closure, die wir hier der Variablen c7 zuweisen, gibt an sich überhaupt keinen Sinn. Sie soll eine Methode substring() aufrufen, aber eine solche Methode gibt es weder in der Closure selbst noch in der umgebenden Klasse, die hier ein Skript ist. Normalerweise würde ein Aufruf der Closure eine MissingMethodException hervorrufen. Vorher weisen wir der Closure aber noch einen String als Delegate-Objekt zu, und das hat zur Folge, dass die Closure den substring()-Aufruf an eben diesen String weiterreicht – und hier wird er dann auch ausgeführt, so dass der Teilstring von „Schöne neue Welt“ ab Indexposition 12 ausgegeben wird.

Warnung: An diesem simplen Beispiel sehen Sie schon, dass Konstrukte wie das Closure-Delegate nicht unbedingt geeignet sind, Ihre Programme übersichtlicher zu gestalten. Sie eröffnen zwar interessante neue Möglichkeiten; eine davon sind die Builder-Klassen, auf die wir weiter unten eingehen.. In normalen Anwendungen sollte man solche Mittel aber – wenn überhaupt – nur vorsichtig und gut dokumentiert anwenden.

Beispiel: Logging

Bearbeiten

Das folgende Beispiel soll einige der Möglichkeiten, die durch Closures gegeben sind, im Zusammenhang zeigen. Wir wollen eine Klasse schreiben, die irgendetwas tut, was uns hier nicht weiter interessieren soll, dies aber in einer flexiblen Weise protokollieren. Die Protokollmethode ist einfach durch eine Property definiert, der eine Closure zur Ausgabe des Protokolltextes zugewiesen werden kann.

class LoggingWorker {

  Closure log = { stufe, text ->}

  def tuwas () {
    log(0, "Tuwas begonnen")
    //...
    log(1, "Fehler aufgetreten")
    //...
    log(0, "Tuwas beendet")
  }
}

def worker = new LoggingWorker()
worker.log = { stufe, text -> println (['INFO','FEHLER'][stufe]+': '+text) }
worker.tuwas()