Android-Entwicklung mit Groovy

Seite 3: AST und Traits

Inhaltsverzeichnis

Der Groovy-Compiler bietet die Möglichkeit, in den Kompilierungslauf einzugreifen und den Abstract Syntax Tree (Objektstruktur des Quellcodes) zu analysieren und zu verändern. Dies reduziert Boilerplate-Code und löst Querschnittsprobleme wie Autorisierung, Tracing oder Ähnliches. In Java hilft in dem Fall oft nur aspektorientierte Programmierung (AOP).

Über Annotationen im Quellcode aktiviert man die AST-Transformationen. So erzeugt beispielsweise @Immutable an einer Klasse den gesamten nötigen Code, um sie "Immutable" zu setzen. Ein Beispiel in Java:

public final class ToBeImmutable {
private final String variable;
public ToBeImmutable(String variable) {
this.variable = variable;
}
public String getVariable() {
return variable;
}
@Override
public String toString() {
return "ToBeImmutable(variable:" + variable + ")";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((variable == null) ? 0 :
variable.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ToBeImmutable other = (ToBeImmutable) obj;
if (variable == null) {
if (other.variable != null)
return false;
} else if (!variable.equals(other.variable))
return false;
return true;
}
}

Dasselbe ist in Groovy deutlich schlanker:

import groovy.transform.Immutable
@Immutable final class ToBeImmutable {
String variable
}

getVariable() und setVariable() werden vom Groovy Compiler hinzugefügt (siehe Groovy Beans). Weitere Beispiele für AST-Transformationen lassen sich in der folgenden Tabelle nachlesen.

Annotation Beschreibung
@Singleton macht die Klasse zu einem Singleton
@Bindable erweitert die Klasse um PropertyChangeSupport und wirft Events nach der Änderung
@Vetoable erweitert die Klasse um PropertyChangeSupport und wirft Events vor der Änderung
@ToString erzeugt eine .toString()-Methode
@EqualsAndHashCode erzeugt eine .equals()- und eine .hashCode()-Methode
@TupleConstructor erzeugt je einen Konstruktor für alle Kombinationen an Properties der Klasse
@Canonical Kombination von @ToString, @EqualsAndHashCode und @TupleConstructor
@InheritConstructors erzeugt einen Konstruktor für alle Konstruktoren der Superklasse
@Delegate erzeugt Delegate-Methoden für alle Methoden des als Delegate angegeben Felds
@Lazy erzeugt Code für die späte Initialisierung eines Feldes
@Builder erzeugt Methoden für alle Properties nach dem Java-Builder Pattern
@Log, @Log4J, @Log4J2 erzeugt eine Variable log mit einem Logger-Object (java.utils, Log4j oder Log4J2)

Außerdem lässt sich über die Annotationen @TypeChecked und @CompileStatic für Klassen und Methoden steuern, ob und wo der Quellcode beim Kompilieren statisch zu prüfen oder statisch zu kompilieren ist. Dabei verliert man zwar statische Hilfsmechanismen (zum Beispiel Json- und MarkupBuilder), dafür ist der erzeugte Bytecode allerdings kleiner und genau so schnell wie von Java kompilierter Code.

Auf Android-Geräten ist Größe und Performance sehr wichtig. Deshalb ist es angeraten, alles statisch zu kompilieren und nur die Methoden davon auszunehmen, die dynamische Funktionen benötigen.

Bei Mehrfachvererbung kann das Diamond-Problem enstehen, das im Folgenden kurz beschrieben ist. Klasse A implementiert eine Methode getName(). Die Klassen B und C werden jeweils von A abgeleitet und überschreiben jeweils die Methode getName(). Wenn nun die Klasse D von B und C abgeleitet wird und getName() nicht überschreibt – welche Implementierung erbt dann D?

Groovy löst das Problem mit Traits, die Code wie Klassen enthalten können, aber wie Interfaces mit implements notiert werden. Im Zweifel hat der letzte Trait die höchste Präzedenz und gewinnt.

Es ist zwar nicht empfohlen, public-Variablen in Traits zu benutzen, aber dennoch möglich. Sie werden dann allerdings nach dem Muster <Klassenname>__<Variablenname> umbenannt.