Groovy: GroovyBeans

Aus Wikibooks

In Java bezeichnet man Klassen, die nach bestimmten Konventionen programmiert sind, als JavaBeans. Eine der Konventionen besteht in der Regel, dass eine JavaBean über Properties (Eigenschaften) verfügt, auf die von außen nur mit Hilfe von Zugriffsmethoden (Akzessoren und Mutatoren) zugegriffen werden kann. Wenn es also eine Property namens eigenschaft gibt, dann kann sie aus einer anderen Klasse nur mit der Methode getEigenschaft() ausgelesen und mit der Methode setEigenschaft() gesetzt. Was sich hinter eigenschaft im Einzelnen verbirgt, ist nicht gesagt. Häufig aber implementiert die Klasse eine private Member-Variable gleichen Namens, auf die über die beiden Methoden zugegriffen wird. Umgangssprachlich werden die Zugriffsmethoden meist einfach als Setter- und Getter-Methoden bezeichnet.

Ursprünglich war die JavaBean-Konvention als Standard für die Gestaltung von Oberflächenkomponenten gedacht, die dann aufgrund der Namenslogik mit Werkzeugen konfiguriert werden konnten, die diese Komponenten nicht im Einzelnen kennen mussten. Inzwischen ist es aber weitgehend Usus geworden, die Regeln allgemein bei der Programmierung von Klassen anzuwenden, und es gehört zum Standard, dass man auf Member-Variablen von außen normalerweise nicht direkt, sondern immer über Setter und Getter zugreift.

Im Unterschied zu Java wird die JavaBean-Konvention bezüglich des Zugriffs auf Properties durch die Sprache Groovy direkt unterstützt. Hier nimmt Ihnen der Compiler die Definition der Zugriffsmethoden ab und auch die Syntax für den Zugriff auf Objekt-Properties ist vereinfacht.

Member deklarieren[Bearbeiten]

Grundsätzlich werden die Member einer Klasse (Felder und Methoden) genau so deklariert wie in Java: Sie geben den Typ, bei Bedarf ergänzt durch Typmodifikatoren wie static und private, und den Namen an. Wenn Sie möchten, können Sie, getrennt durch ein Gleichheitszeichen, gleich einen Initialwert zuweisen. Da weder Felder noch Methoden typisiert sein müssen, können Sie die Typangabe auch weglassen. Es muss aber für Groovy erkennbar sein, dass es sich um eine Deklaration handelt, wenn also kein Typmodifikator benötigt wird, muss die Deklaration durch das Schlüsselwort def kenntlich gemacht werden. Hier einige Beispiele für gültige Felddeklarationen:

Integer i // Instanzvariable vom Typ Integer
Integer[] j // Instanzvariable vom Typ Integer-Array
static a // Untypisiertes statisches Feld
private b // Untypisierte privates Instanzvariable
def x = "Start" // Untypisiert Instanzvariable, mit String initialisiert

Bei der Variablen x ist das Schlüsselwort def erforderlich, weil sonst nicht zu erkennen wäre, dass es sich um eine Variablendeklaration handelt. Bei a und b wird es nicht benötigt, weil die Deklaration durch einen Typmodifikator kenntlich ist; allerdings stört es auch nicht, wenn Sie das def trotzdem hinzufügen.

Beachten Sie, dass die Regeln für die Sichtbarkeit der Member etwas von Java abweicht.

  • Die standardmäßige Sichtbarkeit, die gilt, wenn Sie selbst keine Angabe machen, ist bei Methoden public und bei Feldern private, wobei bei Feldern dann automatisch Getter und Setter generiert werden (mehr dazu gleich im Anschluss).
  • Die Package-Sichtbarkeit, die in Java beim Fehlen einer Deklaration angenommen wird, wird in Groovy nicht unterstützt.

Felder und Properties[Bearbeiten]

Wenn Sie in Groovy ein Feld für eine Klasse deklarieren und keine Angaben über die Sichtbarkeit machen, nimmt der Compiler an, dass es sich um ein Property-Feld handelt. Das bedeutet, dass er das Feld selbst als private betrachtet und von sich aus öffentlichen Getter- und Setter-Methoden hinzufügt, sofern diese Methoden nicht bereits explizit definiert sind.

Die folgenden fünf mageren Zeilen enthalten also die vollständige Definition einer Bean mit drei Properties.

class Datum1 {
  Integer jahr
  Integer monat
  Integer tag
}

Dieser Code ist vollständig äquivalent mit der folgenden ausführlichen Version.

class Datum1 {
  private Integer jahr
  private Integer monat
  private Integer tag
  Integer getJahr() {
    return jahr
  }
  void setJahr (Integer jahr) {
    this.jahr = jahr
  }
  Integer getMonat() {
    return monat
  }
  void setMonat (Integer monat) {
    this.monat = monat
  }
  Integer getTag() {
    return tag
  }
  void setTag (Integer tag) {
    this.tag = tag
  }
}

Groovy fügt die beiden Zugriffsmethoden tatsächlich als Bytecode hinzu. Dies ist insofern wichtig, als Sie vielleicht auch aus normalen Java-Programmen über die Getter- und Setter-Methoden zugreifen möchten, und dann müssen diese auch als richtige Methoden vorhanden sein. Damit sind GroovyBeans in Umgebungen wie Inversion-of-control-Containern wie Spring und Apache Avalon einsetzbar, bei denen die Einhaltung der Bean-Konventionen eine besondere Rolle spielt, ohne dass Sie sich mit der Programmierung der Zugriffsmethoden aufhalten müssen.

Tipp: Eine Klasse für Datumsangaben wird Ihnen als Beispiel in diesem Buch von nun an öfter begegnen. Der Grund dafür ist nicht nur, dass sie sich als einfach verständliches Beispiel gut eignet. Sie kann auch – in einer etwas weiter ausgebauten Form – ausgesprochen nützlich sein, denn die zum JDK gehörenden Kalenderklassen sind zwar recht vielseitig, aber nicht gerade ein Musterbeispiel an Benutzerfreundlichkeit. An unseren in Groovy geschriebenen Datumsklassen können Sie sehen, wie einfach es Groovy Ihnen macht, komplizierte Dinge mit einer handhabbaren Kapsel zu versehen.

GroovyBeans implementieren auch Interfaces. Angenommen, sie haben ein Interface DatumIf (egal, ob Groovy oder Java programmiert):

public interface DatumIf {
  Integer getJahr()
  void setJahr (Integer jahr)
  Integer getMonat()
  void setMonat (Integer monat)
  Integer getTag()
  void setTag (Integer tag)
}

Als Implementierung genügt die folgende kurze Groovy-Klasse, denn die Getter und Setter werden ja vom Compiler hinzugeneriert.

class Datum1 implements DatumIf {
  Integer jahr
  Integer monat
  Integer tag
}

Der Groovy-Compiler hält sich jedoch vornehm zurück, wenn Sie selbst schon tätig geworden sind, also eigene Getter oder Setter geschrieben haben. Das gibt Ihnen die Möglichkeit, zusätzliche Funktionalität wie Plausibilitätsprüfungen oder Umformatierungen unterzubringen. Ein typisches Beispiel wäre Folgendes:

class Datum1 implements DatumIf {
  Integer jahr
  ...
  void setJahr (Integer jahr) {
    if (jahr<1800 || jahr>2100) {
      throw new IllegalArgumentException("Ungültiges Jahr: $jahr")
    }
  this.jahr = jahr
  ...
}

Während die Getter-Methode getJahr() wie auch die übrigen Getter und Setter nach wie vor von Groovy generiert wird, haben wir hier einen manuellen Setter, der eine Eingabeprüfung vornimmt.

Warnung: Wenn Sie Getter oder Setter zu Groovy-Properties selbst schreiben, müssen Sie akribisich auf die korrekte Signatur achten. So dürfen Sie im obigen Beispiel beispielsweise nicht den Setter mit def statt mit void definieren, sonst kann es passieren, dass Groovy trotzden seinen eigenen Setter generiert und Ihrer nie aufgerufen wird.

Wir können diese Möglichkeit natürlich auch nutzen, um mit unserer Datumsklasse einfach den Java-eigenen Kalender zu kapseln und etwas leichter nutzbar zu machen.

class KalenderDatum {
  Calendar calendar = new GregorianCalendar()
  void setJahr (Integer jahr) {
    calendar.set(Calendar.YEAR,jahr)
  }
  Integer getJahr () {
    calendar.get(Calendar.YEAR)
  }
  // Hier die übrigen Getter und Setter
  // nach demselben Muster.
}

Nun können wir sogar das aktuelle Datum bekommen und weiterhin die Bestandteile des Datums bequem einzeln setzen und auslesen:

groovy> k = new KalenderDatum()
groovy> println k.getJahr()
groovy> k.setJahr(1949)
groovy> println k.getJahr()
2007
1949

Die erste ausgegebene Jahreszahl stammt aus dem innerhalb unseres KalenderDatum frisch instanziierten GregorianCalendar, und die zweite haben wir im Skript selbst gesetzt. Dies können Sie natürlich in Java genau so machen; die Besonderheiten des Kapselns in einer Groovy-Klasse werden erst weiter unten deutlicher werden. Immerhin können wir aber das Datum in unserem KalenderDatum schon einmal sehr bequem setzen:

groovy> k = new KalenderDatum()
groovy> k.setCalendar(new GregorianCalendar(1962,3,29))
groovy> println k.getJahr()
1962

Groovy-Properties können auch als final deklariert werden. In diesem Fall werden naürlich nur die Getter generiert, da die Setter keinen Sinn ergeben würden. Die folgende Variante der Datumsklasse ist immutabel, d.h. ihre Werte können nur im Konstruktor gesetzt und später nur noch gelesen werden.

class ImmutablesDatum {
  final Integer jahr
  final Integer monat
  final Integer tag
  ImmutablesDatum (Integer jahr, Integer monat, Integer tag) {
    this.jahr = jahr
    this.monat = monat
    this.tag = tag
  }
}

Nun kann es aber sein, dass Sie eine Klasse brauchen, in der die Felder zwar nicht immutabel sein sollen, aber trotzdem nicht von außen gesetzt werden können. Zum Beispiel weil Sie benannte Parameter verwenden möchten. In diesem Fall hilft Ihnen der obige Trick nicht weiter, denn sobald eine Property nicht final ist, lässt sich das Generieren des Setters nicht verhindern. In diesem Fall könnten Sie den Umstand ausnutzen, dass Groovy generell keine Getter und Setter generiert, sobald Sie die Sichtbarkeit eines Feldes selbst explizit festlegen.

// Achtung, Beispiel funktioniert nicht wie erwartet.

class NurLeseDatum {
  private Integer jahr
  private Integer monat
  private Integer tag

  Integer getJahr() {
    return jahr;
  }
  Integer getMonat() {
    return monat;
  }
  Integer getTag() {
    return tag;
  }
}

Diese Klasse können Sie jetzt mit benannten Parametern instantiieren, z.B. new NurLeseDatum(tag:24,monat:12,jahr:2000). Leider lassen sich aber immer noch die Felder von außen verändern, denn gegenwärtig missachtet Groovy leider immer noch die Privatsphäre der Klassen.

Achtung: Verlassen Sie sich in Groovy 1.x nicht darauf, dass als private oder protected deklarierte geschützt sind.

Auf Properties zugreifen[Bearbeiten]

Wenn Sie auf die Property eines Objekts aus einem anderen Objekt zugreifen möchten, dann können Sie dies natürlich in der von Java gewohnten Weise tun, indem Sie die Getter und Setter der Property aufrufen. Wir probieren dies mit einem Skript aus, das unsere obige Klasse Datum1 instanziiert.

def datum = new Datum1()
datum.setJahr(1984)
println datum.getJahr()

Dies ist korrekt, es ist aber nicht das, was man in Groovy einen Property-Zugriff nennen würde; es ist schlicht der Aufruf zweier Methoden (die freilich genau dies tun). In Groovy greifen Sie auf die Properties eines Objekts zu, als wären es öffentliche Felder.

def datum = new Datum1()
datum.jahr = 1984
println datum.jahr

Daneben gibt es noch eine weitere Möglichkeit. Sie können auf die Properties einer Klasse auch ähnlich den Elementen eines Arrays zugreifen; allerdings ist dabei allerdings der Index nicht numerisch, sondern ein String (in anderen Sprachen bezeichnet man so etwas als "assoziatives Array".

def datum = new Datum1()
datum['jahr'] = 1984
println datum['jahr']

Der Vorteil der alternativen Schreibweisen liegt weniger in deren Eleganz (über die man ohnehin streiten kann), sondern darin, dass Sie auf diese Weise die Flexibilität und Dynamik der Sprache Groovy erst so recht nutzen können. Wenn Sie referenz.feldname oder referenz['''feldname'''] schreiben, anstatt die Getter und Setter zu benutzen, ist es nämlich egal, wie die Property implementiert ist; es muss nur die entsprechende Information geliefert werden. Diese Zugriffe funktionieren sogar, wenn datum auf eine Map verweist. Probieren Sie es:

def datum = new HashMap()
datum.jahr = 1984
println datum.jahr
datum['monat'] = 12
println datum['monat']

Groovy verfolgt ein Prinzip, dass der Zugriff auf verschiedene Arten von Objekten möglichst gleichartig sein soll. Das macht es nicht nur einfacher, sich zu merken, was bei einem bestimmten Objekt zu tun ist. Es ist auch das Gegenstück zu der dynamischen Typisierung, die Groovy Ihnen bietet. Wenn Sie eine Methode haben, die sich für die Jahreszahl eines Datums interessiert, ist es völlig egal, wie das entsprechende Objekt implementiert ist, Hauptsache es liefert unter dem Namen jahr die entsprechende Information.

Was geschieht nun genau, wenn ein Groovy-Programm eine Referenz auf eine Property auflöst:

  1. Wenn es sich um einen lokalen Zugriff handelt und es ein Feld mit dem genannten Namen gibt, wird dieses direkt adressiert.
  2. Wenn das Objekt das Interface Map implementiert, wird deren Methode get() bzw. set() aufgerufen.
  3. Wenn es eine korrespondierende Zugriffsmethode (Getter bzw. Setter), wird diese für den Zugriff verwendet.
  4. Wenn es ein Feld mit dem Namen der Property mit passenden Zugriffsrechten gibt, wird dieses ausgelesen bzw. gesetzt.
  5. Wenn in der betreffenden Klasse eine Methode mit der Signatur Object get(String) bzw. void set(String,Object) existiert, wird diese mit dem Namen der Property als Argument aufgerufen.

Aus der Option 2 folgt, dass Sie auf die Elemente einer Map wie auf die Properties eines Objekts zugreifen können. Im obigen Beispiel bedeutet dies also:

def datum = new HashMap()
datum.jahr = 1984 // entspricht: datum.put('jahr',1984)
println datum.jahr // entspricht: println datum.get('jahr')
datum['monat'] = 12 // entspricht: datum.put('monat',12)
println datum['monat'] // entspricht: println datum.get('monat')

Die letzte der fünf Möglichkeiten erlaubt es Ihnen, mit Properties recht kreativ umzugehen. Was die Methoden get() und set() im Einzelnen anstellen, interessiert Groovy nicht, und was Sie mit den übergebenen bzw. angeforderten Werten machen, bleibt gänzlich Ihnen überlassen. Sowohl get() als auch set() werden nur aufgerufen, wenn keine der anderen Möglichkeiten anwendbar ist. Damit bieten Sie Ihnen gewissermaßen einen letzten Ausweg, wenn Sie Zugriffe auf nicht existierende Properties abfangen möchten. Wir können dies mit Hilfe einer leicht erweiterten Variante der ersten Version der Datumsklasse zeigen.

class Datum2 {
  Integer jahr
  Integer monat
  Integer tag
  Integer getJahrhundert() {
    return jahr/100
  }
  def get(String propertyname) {
    println "Lese unbekannte Property: $propertyname"
  }
  def set(String propertyname, Object wert) {
    println "Setze unbekannte Property: $propertyname=$wert"
  }
}

Dazu ein neues Experiment mit groovysh:

groovy> d = new Datum2()
groovy> d.jahr = 2000
groovy> println d.jahr
groovy> println d.jahrhundert
groovy> d.jahrhundert = 21
groovy> println d.wochentag
2000
20
Setze unbekannte Property: jahrhundert=21
Lese unbekannte Property: wochentag
null

Das Ergebnis zeigt, dass die Property jahr wie bisher direkt gesetzt und ausgelesen und die Property jahrhundert über die explizit geschriebene Zugriffsmethode getJahrhundert() erreicht wird; nur beim schreibenden Zugriff auf die Property jahrhundert sowie beim Lesen von wochentag muss Groovy auf get() und set() ausweichen, was an den beiden Meldungen zum Schluss sehen ist. (Die null am Ende rührt daher, dass wir die nicht existierende Property d.wochetag ausgeben wollten, dabei aber an die get()-Methode geraten sind, die ja für get('wochentag') kein Ergebnis liefert.

Der Flexibilität noch lange nicht genug. Um die obigen Property-Zugriffsarten ausführen zu können, generiert Groovy die beiden Methoden setProperty(String,Object) und getProperty(String). Auch diese können Sie überschreiben und damit interessante Kunststücke vollführen; dies gehört aber schon zum Thema Meta-Programmierung, auf die wir in Dynamisches Programmieren zu sprechen kommen.

Eine weitere, in diesen Zusammenhang passende und von Groovy generierte Methode ist getProperties(). Sie ermöglicht es Ihnen, eine Map der in einem Objekt vorhandenen Properties selbst wie eine Property auszulesen.

groovy> def d = new Datum2()
groovy> d.jahr = 1990
groovy> d.monat = 10
groovy> d.tag = 3
groovy> println d.properties
["tag":3, "jahrhundert":19, "monat":10,
"metaClass":groovy.lang.MetaClassImpl@208506[class Datum2], "jahr":1990,
"class":class Datum2]

Das Ergebnis ist vielleicht etwas erklärungsbedürftig. Die Properties tag, monat und jahr kennen wir, denn die haben wir selbst ins Leben gerufen. In jahrhundert spiegelt sich die selbst geschriebene Getter-Methode wider. Die Property class ist auch eine Bekannte: Es ist die Java-Klasse des Objekts, also das Ergebnis eines Aufrufs von getClass(). Neu und Groovy-spezifisch ist dagegen die Property metaClass, die von Groovy gesetzt wird und das standardmäßige Verhalten der zugeordneten Objekte bestimmt.

Bei der Suche nach Properties orientiert sich getProperties() an den in der Klasse definierten konventionellen Getter-Methoden. Und so findet sie in dem obigen Beispiel die explizit programmierte Methode getJahrhundert(), die anhand unserer Property-Felder generierten Methodem getJahr(), getMonat() und getTag(), die zur Java-Basisklasse Object gehörende Methode getClass() und schließlich die von Groovy generierte Methode getMetaClass(). Fällt Ihnen etwas auf? Die ja offenbar vorhandene Methode getProperties() fehlt. Das ist vielleicht nicht ganz konsequent, verhindert aber eine Endlosschleife.

Bei manchen Klassen funktioniert getProperties() etwas anders. Wir benutzen noch einmal unsere HashMap von oben, mit der wir oben nur eine Bean vorgegaukelt haben.

Die Tatsache, dass getProperties() nur die „gemeinen“ Getter berücksichtigt, führt auch dazu, dass es bei vorgegaukelten Beans wie dem obigen HashMap-Beispiel nicht funktioniert.

groovy> def datum = new HashMap()
groovy> datum.jahr = 1984
groovy> datum['monat'] = 12
groovy> printlen datum.getProperties()
["empty":false, "class":class java.util.HashMap,
 "forNullKey":null]

Hier bekommen wir konsequenterweise die Ergebnisse von isEmpty(), getClass() und getForNullKey() zu sehen, nicht aber die vermeintlichen Properties jahr und monat. An diesem Beispiel können Sie übrigens auch erkennen, dass properties doch keine richtige Property ist, auch wenn es eine vordefinierte Methode getProperty() gibt. Bei der HashMap würde die Schreibweise datum.properties versuchen, ein Map-Element „properties“ auszulesen, das es nicht gibt, und als Folge nur null liefern. Daher mussten wir die Methode hier direkt aufrufen.

Warnung: Generell empfiehlt es sich, lieber die Getter- oder Setter-Methoden anstelle der Property-Notation, wenn im Vorhinein nicht mit Sicherheit klar ist, mit was für einem Objekt Sie arbeiten. Beispielsweise besteht ein verbreiteter Fehler in Groovy-Programmen darin, dass die Klasse eines Objekts als Property abgefragt wird, zum Beispiel so:
println meinObjekt.class.name

Das geht meistens gut, weil meinObjekt.class in meinObjekt.getClass() übersetzt wird. Sobald meinObjekt aber eine Map ist, versucht Groovy, die Methode meinObjekt.get('class') auszuführen. Und wenn die Map nicht zufällig unter dem Schlüssel „class“ einen gültigen Wert gespeichert hat, führt dies zu eine NullPointerException.

Schreibweisen für Property-Namen[Bearbeiten]

Da ein Zugriff auf eine Property nicht unbedingt gleichbedeutend mit dem Zugriff auf ein Bean-Feld ist, kann es sein, dass der Name einer Property Zeichen enthält, die in einem Java-Feldnamen nicht erlaubt sind, zum Beispiel Leerzeichen, Bindestriche usw. Um die Verwendung solcher Namen auch in der Punkt-Notation zu ermöglichen, erlaubt Groovy die Angabe des Feldnamens in Anführungszeichen.

groovy> def datum = new HashMap()
groovy> datum.'tag im jahr' = 100
groovy> println datum.'tag im jahr'

Sie können sogar doppelte Anführungszeichen verwenden und mittels GString-Interpolation die Property-Namen zusammensetzen.

groovy> def feld = 'tag'
groovy> def datum = new HashMap()
groovy> datum."$feld im jahr" = 100
groovy> println datum."$feld im jahr"

Wir nutzen diesen Umgang mit Properties in unserer obigen Klasse KalenderDatum aus, in der wir zum Speichern des Datums einfach ein Java-Calendar-Objekt verwenden. Die einzelnen Datums- und Zeitfelder dieser Klasse werden durch eine spezielle get()- und spezielle set()-Methode ausgeführt, die jeweils als erstes bzw. einziges Argument eine Indexnummer des gewünschten Feldes annimmt.

Für diese Indexnummer sind in der Calender-Klasse Konstanten vordefiniert. Wir ergänzen nun KalenderDatum um eine get()- und eine set()-Methode, die als Auffangnetz für Property-Zugriffe dienen, für die es keine spezifischen Getter und Setter gibt. Die Aufrufe dieser Methoden leiten wir nun an die get()- und set()-Methoden des Calendar-Objekts weiter, und wandeln dabei die Property-Namen durch String-Interpolation in die Namen der Konstanten für die Felder um. Das klingt komplizierter als es ist; sehen Sie es sich einfach mal an:

class KalenderDatum {
  Calendar calendar = new GregorianCalendar()
  def get(String propertyname) {
    calendar.get(Calendar."$propertyname")
  }
  def set(String propertyname, wert) {
    calendar.set(Calendar."$propertyname", wert)
  }
}

Nun können wir einfach die Konstantennamen aus der Calendar-Klasse als Property-Namen für KalendarDatum verwenden und recht elegant die Datumsfelder auslesen oder setzen, als wären es Properties unserer KalenderDatum-Klasse.

groovy> k = new KalenderDatum()
groovy> k.setCalendar(new GregorianCalendar(1999,11,12))
groovy> println k.DAY_OF_MONTH+'.'+k.MONTH+'.'+k.YEAR
groovy> k.DAY_OF_MONTH = k.DAY_OF_MONTH+42
groovy> println k.DAY_OF_MONTH+'.'+k.MONTH+'.'+k.YEAR
12.11.1999
23.0.2000

In der Ausgabe erscheint erst das Datum, das wir per neuem GregorianCalendar frisch gesetzt haben, und – nachdem wir die Tage um 42 (= 6 Wochen Urlaub) erhöht haben – ein entsprechend weiter geschaltetes Datum. Natürlich stören uns hier die in Property-Namen in Großbuchstaben, und wir hätten gerne, dass get() und set() auch mit Aufruf k.dayOfMonth anstelle von k.DAY_OF_MONTH zurechtkommen, denn wir wollen ja keinen konstanten Wert auslesen. In Java müssten wir nun eine lange verschachtelte if-Verzweigung programmieren. Zum Glück haben wir es in einer dynamischen Sprache wie Groovy leichter.

class KalenderDatum {
  Calendar calendar = new GregorianCalendar()

  def get(String propertyname) {
    calendar.get(calendarConst(propertyname))
  }

  void set(String propertyname, wert) {
    calendar.set(calendarConst(propertyname), wert)
  }

  private calendarConst(String propertyname) {
    try {
      def constname = propertyname.replaceAll('[A-Z]','_$0').toUpperCase();
      return Calendar."$constname"
    } catch (MissingPropertyException ex) {
      ex.printStackTrace()
      throw new MissingPropertyException(propertyname,KalenderDatum)
    }
  }
}

Wir haben eine kurze Methode namens calendarConst() eingeführt, die den Property-Namen von einer CamelCase-Notation mit Hilfe eines regulären Ausdrucks und der String-Methode toUpperCase() in einen Konstantennamen aus Großbuchstaben und Unterstrichen umsetzt und dann die Calendar-Konstante als dynamische Property ermittelt. Aus dem Argument "dayOfMonth" macht sie also "DAY_OF_MONTH" und ermittelt dann den Konstantenwert von Calendar.DAY_OF_MONTH. Außerdem fängt sie die MissingPropertyException von Groovy ab, falls es den konvertierten Konstantennamen nicht gibt, und löst dann dieselbe Exception mit dem ursprünglichen Property-Namen und dem Namen der KalenderDatum-Klasse aus, da andernfalls die Fehlermeldungen sehr verwirrend sind. Nun können wir die Kalenderwerte abrufen, als wären es ganz normale Properties.

groovy> k = new KalenderDatum()
groovy> k.setCalendar(new GregorianCalendar(1999,11,12))
groovy> println k.dayOfMonth+'.'+k.month+'.'+k.year
groovy> k.dayOfMonth += 42
groovy> println k.dayOfMonth+'.'+k.month+'.'+k.year
12.11.1999
23.0.2000

Ein letztes Problem gilt es allerdings noch zu lösen: eine Datumsangabe wie 23.0.2000 kann man nicht gerade als intuitiv ansehen. Der GregorianCalendar zählt die Monate von 0 bis 11; das würden wir unserem KalenderDatum aber gerne abgewöhnen. Wir erinnern uns der konventionellen Zugriffsmethoden, die ja Vorrang vor dem generischen get() und set() haben, und fügen einen Getter und einen Setter für die Monatsangabe hinzu:

def getMonth() {
  calendar.get(Calendar.MONTH) + 1
}
void setMonth (wert) {
  calendar.set(Calendar.MONTH,wert-1)
}

Jetzt erfahren alle Zugriffe auf den Monat eine besondere Behandlung, während die Zugriffe auf alle anderen Datumsfelder nach wie vor über unsere generischen Methoden laufen. Um das Ganze rund zu machen, fügen wir auch gleich einen vernünftigen Konstruktor hinzu, der Tag, Monat und Jahr als Zahlen erwartet und das Datum entsprechend setzt, und eine toString()-Methode, der wir etwas Sinnvolles entnehmen können.

Calendar calendar = new GregorianCalendar(0,0,0,0,0,0)
KalenderDatum(dayOfMonth,month,year) {
  this.dayOfMonth = dayOfMonth
  this.month = month
  this.year = year
}
String toString() {
  "${dayOfMonth}.${month}.${year}"
}

Sie sehen an dem Beispiel, wie wir auch innerhalb der Klasse mit den Properties ganz genau so umgehen können, als wären es Felder der Klasse. Ein kurzer Test mit einem interaktiven Test zeigt, dass es auch funktioniert, und zwar mit korrekten Monatszahlen.

groovy> k = new KalenderDatum(12,12,1999)
groovy> println k
groovy> k.dayOfMonth += 42
groovy> println k
12.12.1999
23.1.2000

Das sieht alles ganz harmlos aus. Aber halten Sie sich mal vor Augen, dass beispielsweise das Erhöhen eines Kalenderdatums um einen Tag in Java mit dem Calendar so aussieht:

cal.set(Calendar.DAY_OF_MONTH,cal.get(Calendar.DAY_OF_MONTH)+1);

Nett, aber haben Sie dasselbe schon mal mit unserer kleinen KalenderDatum-Klasse gesehen?

k.dayOfMonth ++

So können wir die Datumsklasse schon einmal belassen, es wird sich aber zeigen, dass mit Hilfe von Groovy noch einige weitere reizvolle Verbesserungen möglich sind.

Fluss ohne Wiederkehr

Achtung, Sie haben soeben den sicheren Boden der konventionellen Java-Programmierung verlassen. Wie Marilyn Monroe auf ihrem Floß passieren Sie gerade die Grenze zu einer Welt voller Wunder und Abenteuer, in der vieles möglich ist, was Sie bisher nicht für machbar hielten. Es ist aber auch eine Welt voller Gefahren, in der Dinge passieren, mit denen Sie nie gerechnet hätten. Und es gibt keinen Weg zurück: Dynamische Klassen wie unser KalendarDatum, bei denen Sie auf Properties zugreifen können, die es gar nicht gibt, funktionieren in Java nur rudimentär und können dort nicht sinnvoll verwendet werden. Und rechnen Sie damit, dass Ihr Kollege, der von Groovy noch nicht so viel weiß, Ihnen ebenso wenig folgen kann wie Robert Mitchum Marilyn folgen konnte.

Dabei ist dies nur ein Vorgeschmack, in Dynamisches Programmieren werden Sie ganz andere Zaubereien mit Groovy kennen lernen...