Übersicht:
Sicheres Dereferenzieren
Groovy hat neben dem . Operator eine weitere
Möglichkeit zum Dereferenzieren und Selektieren bekommen:
den ?. Operator. Mit diesem ist es möglich, das
Testen auf null -Referenzen zu unterlassen, und
das Auswerten des Folgeausdrucks zu unterdrücken. Für den
Fall das die Referenz null ist, wird der Ausdruck
wird ist zu null ausgewertet.
class A {
String s = "foo";
}
A a = null;
assert null == a?.s; //!!
a = new A();
assert "foo" == a?.s;
In dem Beispiel
würde das normale Dereferenzieren mittels a.s zu
einer NullPointerException führen. Mit Hilfe des
neuen Operators kann man sich das häufige Testen auf Gleichheit
mit null sparen.
Indizierter Zugriff auf Arrays und Listen
In Groovy verhalten sich Listen und Arrays sehr
ähnlich. Beide sind mit eckigen Klammer indizierbar. Es ist auch
möglich, die Indizierung nicht wie gewohnt von vorn, sondern von
hinten zu beginnen. a[-1] liefert das letzte
Element von a; a[-2] liefert das vorletzte Element,
usw. Dies ist eine praktische Abkürzung, allerdings werden
dadurch einige IndexOutOfBoundsExceptions
unterdrückt.
Optionale Parameter, benannte Parameter & Parameterlisten
In vielen Programmiersprachen werden Parameter von Funktionen
anhand der Reihenfolge der formalen und aktuellen Parameter
zugeordnet. Der erste aktuelle Parameter entspricht dabei dem
ersten formalen Parameter usw. In Groovy ist es möglich,
wie auch in Python und VHDL, Parameter anhand ihres Namens zu
identifizieren.
String concat(Map args)
{
return args.get("a", "anfang") + args.get("limiter", "") + args.get("b", "ende");
}
assert "schallrauch" == concat(a:"schall", b:"rauch", limiter:"");
assert "anfang bis ende" == concat(limiter:" bis ");
In dem
Beispiel sieht man die Syntax, die für benannte Parameter
genutzt wird. Wie man an der zweiten Assertion sehen kann,
müssen nicht alle Parameter angegeben werden, da man in
der concat Methode Standardwerte genutzt hat. In
Groovy sind benannte Parameter immer auch optionale
Parameter. Tatsächlich wird bei der Nutzung von benannten
Parametern immer nur ein Parameter übergeben: eine Map, die
Strings (die Parameternamen) auf Objects (die Parameterwerte)
abbildet. Wird ein Parameter ausgelassen, dann zeigt der
zugehörige String auf null .
Ab der Version 1.5 ist es auch in Java möglich, beliebige
viele Parameter zu übergeben, indem man die ...
Syntax bei der Funktionsdeklaration nutzt, was man auch
unter dem Namen variable Parameterliste kennt. In
Groovy nutzt man stattdessen
ein Object[] als letzten
Parameter. Auch in Java ist der letzte Parameter implizit
bei der ... Notation ein
Array.
int sum(Object[] l)
{
int res = 0;
for(o in l) {
res += o;
}
return res;
}
assert sum(2, 3, 37) == 42;
assert sum() == 0;
Im Vergleich mit Java sind die optionalen Parameter
tatsächlich neu. Im Gegensatz zu den variablen Parameterlisten
bleibt die maximale Anzahl der aktuellen Parameter begrenzt.
String concat(String msg, String extension = "")
{
return msg + extension;
}
assert "schall" == concat("schall");
assert "ab" == concat("a", "b");
In dem
Beispiel kann die Methode concat mit einem oder
mit zwei Parametern aufgerufen werden. Bislang sind bei der
Deklaration der optionlen Parameter nur Literale und keine
Referenzen möglich.
Einfache Typen
Gegenüber Java gibt es in Groovy einige Besonderheiten bei der
Typisierung. Es gibt keine einfachen Typen mehr, diese werden durch
die entsprechende Wrapper-Klasse ersetzt.
int i = 42;
println i.class
j = 42.0
println j.class
double k = 42
println k.class
Ausgabe:
class java.lang.Integer
class java.math.BigDecimal
class java.lang.Double
Trotzdem sind alle gewöhnlichen Operatoren für Zahlen
nutzbar, zum Beispiel +, -, ++ . Beim Konvertieren von
Zahlen wird darauf geachtet, dass keine Informationen verloren
gehen. Somit gilt: 1/2 == 0.5
Leider gilt aber immer noch:
(1/3)*3 != 1
BigDecimal schafft zwar einen beliebig grossen Wertebereich, die
Genauigkeit ist aber immer fix.
Getter und Setter
In Java wird automatisch ein öffentlicher Konstruktor generiert,
wenn der Programmierer keinen explizit angibt. In Groovy
werden zusätzlich zu jedem Objekt-Attribut
T xxx; mit der
Sichtbarkeit default eine set-Methode
public void setXxx(T value) { this.xxx = value; }
und eine get-Methode
public T getXxx() { return this.xxx; }
generiert. Dies erleichtert das Implementieren von Klassen mit
vielen einfachen Attributen und reduziert die sonst hohe
Redundanz im Quelltext. Die set- und get-Methoden können
vom Programmierer überschrieben werden, wenn sie mehr Logik
benötigen.
Vorsicht: Dieses Feature wurde noch nicht implementiert.
Strings und Reguläre Ausdrücke
In Java ist es recht umständlich, mehrzeilige Strings
einzugeben. Daher hat man in Groovy die Möglichkeit
HERE-Dokumente einzugeben, wie es in der bash üblich ist:
String msg = """
ein text, der ganz
viele
Zeilen
hat"""
println msg
In den meisten Skriptsprachen, zum Beispiel Tcl, gibt es
Möglichkeiten in einem String-"Literal" Ausdrücke anzugeben,
welche zur Laufzeit ausgewertet werden. Dies ist in Groovy mit
GStrings möglich. Es können darin Variablenbezeichner
durch ihren aktuellen Wert ersetzt werden:
int alter = 42
println "ich bin $alter Jahre alt"
println "ich bin ${alter} Jahre alt"
Außerdem ist es möglich, beliebige Ausdrücke in
sogenannten GStrings auswerten zu lassen:
println """heute ist:
${new Date()}"""
println """gestern war:
${new Date() - 1}"""
Um eine Zeichenkette mit vielen Escape-Sequenzen darzustellen,
kann diese in Schrägstriche (/) geklammert werden. Bei dieser
"slashy" Syntax werden alle umgekehrten Schrägstriche so
übernommen, wie sie eingegeben werden. Dies ist insbesondere
bei Regulären Ausdrücken hilfreich, da in deren Syntax viele
Backslashes vorkommen.
Insgesamt gibt es fünf Darstellungsmöglichkeiten für Strings
und GStrings: 'foo', "foo", '''foo''', """foo""", /foo/
repräsentieren alle die gleiche Zeichenkette. Die
unterschiedlichen Begrenzer haben folgenden Eigenschaften:
- Drei Mal geklammerte Strings können mehrzeilig
eingegeben werden.
- Einfache Anführungszeichen, egal ob ein Mal oder drei
Mal geklammerte, verhindern die Auswertung von GStrings.
Groovy bietet eine Unterstützung für Reguläre Ausdrücke auf
Syntax-Ebene, es werden dazu drei Operatoren bereitgestellt:
s =~ re Ergibt genau dann true,
wenn re in s enthalten ist.
s ==~ re Ergibt genau dann true,
wenn re dem kompletten String s
entspricht.
~String erzeugt einen Regulären
Ausdruck aus dem String vom
Typ java.util.regex.Pattern . Dadurch kann das
häufige Neuerzeugen eines Pattern-Objektes verhindert
werden.
Überladen von Operatoren
In Java sind für Referenztypen lediglich die Vergleichsoperatoren
erlaubt, sowie für die Stringverknüpfung der +
Operator. In Groovy wird viel mehr mit Operatoren als mit
Funktionsnamen gearbeitet, was die Lesbarkeit erhöhen kann. Die
Wrapperklassen der einfachen Datentypen überschreiben
beispielsweise die arithmetischen Operatoren. Das Überladen von
Operatoren kann aber auch vom Anwendungsprogrammierer vorgenommen
werden, da alle Operatoren auf einen Funktionsnamen abgebildet werden.
Die folgende Tabelle, gibt einige Beispiele dazu an.
Operator | Methodenaufruf |
a + b | a.plus(b) |
a - b | a.minus(b) |
a * b | a.multiply(b) |
a ** b | a.power(b) |
a++, ++a | a.next() |
a == b | a.equals(b) |
a > b | a.compareTo(b) > 0 |
switch(a){case b:} | b.isCase(a) |
a[b] | a.getAt(b) |
a[b] = c | a.putAt(b, c) |
Der Index-Operator [] kann dazu genutzt werden, auf
Attribute lesend oder schreibend zuzugreifen. Damit ist es möglich
den Namen des Attributs, welches man bearbeiten möchte, zur Laufzeit
zu berechnen:
class Foo
{
String schall = "laut"
}
String name = "schall"
Foo foo = new Foo();
foo[name] = "42"
println foo.schall
println foo[name]
Mit Switch-Anweisungen lassen sich beliebige Objekte
vergleichen, nicht nur int s wie in Java. Im
folgenden Beispielcode wird überprüft, ob ein Objekt eine Zahl
oder einen String enthält:
Object o = 3.3;
switch(o) {
case String:
println "ein string";
break;
case Integer:
println "eine ganze Zahl";
break;
default:
println "was anderes";
break;
}
Diese Nutzung ist möglich, da in der Klasse Class
der isCase Operator entsprechend überschrieben wurde.
Etwas sinnvoller lässt sich mit Hilfe von Switch-Anweisungen
testen, welchem Regulären Ausdruck ein String entspricht:
//String userInput = "23:59";
String userInput = "42a";
switch(userInput) {
case ~/[0-9]+/:
println "eine Zahl";
break;
case ~/[0-2][0-9]:[0-5][0-9]/:
println "eine Uhrzeit";
break;
default:
println "was anderes";
break;
}
switch Anweisungen entsprechen eher
verschachtelten else-if Strukturen, als
Java-switches . Daher muss bei der Nutzung von
komplexen Datentypen in switch Anweisungen immer
darauf geachtet werde, die korrekte Reihenfolge einzuhalten.
Eine weitere Möglichkeit bieten switch
Anweisungen in Kombination
mit
Closures. Beliebige
boolesche Ausdrücke lassen sich somit kompakt in einer
Anweisung formulieren, ohne auf die else-if
Schachtelungen zurückgreifen zu müssen. Die
Variable it wird immer mit dem zu überprüfenden
Wert belegt.
int userInput = 123;
switch(userInput) {
case {it % 2 == 0}:
println "gerade";
break;
case {it % 10 == 3}:
println "endet mit 3";
break;
default:
println "was anderes";
break;
}
Der Typ Range
Ranges (Bereichstypen) dienen dazu, einen bestimmten Bereich, zum
Beispiel von Zahlen, zu beschreiben. Damit sind Ranges in
index-basierten Schleifen häufig einsetzbar:
for( i in 24..29) {
println i;
}
Der Operator .. erzeugt einen beidseitig inklusiven
Bereich, der Operator ..< einen rechtsseitig
exklusiven Bereich.
Ranges können sehr Speicherplatzt-sparend implementiert
werden. Ein Range besteht aus einer linken und einer rechten Grenze,
sowie einer Funktion zum inkrementieren und einer zum
dekrementieren. Die linke Grenze muß nicht zwangsläufig die
kleinere sein, in dem Fall wird einfach die Reihenfolge umgekehrt.
Ranges können für alle Typen benutzt
werden, die
-
die
++ und -- Operatoren überschreiben
-
java.lang.Comparable implementieren
Somit funktionieren Ranges auch mit Strings. Ein Beispiel:
for( i in ("fol"..<"fop")) {
println i;
}
Ausgabe:
fol
fom
fon
foo
|