12.11.2012
Eclipse Xtend
In dem folgenden Artikel wird Xtend kurz vorgestellt. Inspiriert durch den Vortrag von Sebastian Zarnekow bei der Java User Group Frankfurt habe ich mir Xtend und die Integration in Java und Eclipse angesehen und einen kleinen Artikel dazu geschrieben. Den Blog-Beitrag zu dem Vortrag findet man hier.
Hier werden nur einige wenige Sprachfeatures und Möglichkeiten von Xtend erklärt. Hauptaugenmerk lag auf der Integration von Xtend in Java und die Entwicklungsumgebung Eclipse.
Motivation
Bei den verschiedenen Sprachen für die JVM (Scala, Groovy, Clojure, ...), die ich mir bisher angeschaut habe, war es immer so, dass die Integration in Java nicht besonders gut war. Die Vorträge und Artikel begannen immer mit Wir erstellen jetzt ein Scala-Projekt in der IDE oder Zu Beginn muss ein Groovy-Projekt erzeugt werden. Das ist leider nicht das, was man in der "realen Welt" möchte. Bei Xtend begann der Vortrag mit Wir gehen von einem existierenden Java-Projekt aus. Also die Situation, die man normalerweise vorfindet, wenn man in der Wirtschaft arbeitet und nicht auf der grünen Wiese beginnt.
Installation des Xtend SDK in Eclipse
Die Installation geht mit Eclipse 4.2 (Juno) extrem einfach. Man startet Eclipse und wählt im Menü Help den Punkt Install New Software... (auf dem Mac) aus. In dem Install Wizard wird unter Work with die Update-Site von Juno ausgewählt. Als Filtertext wird Xtend SDK eingetragen, so dass nach dem Xtend SDK gesucht wird. Für dieses wird die Checkbox aktiviert und mit Next bestätigt. In nächsten Dialog werden die Lizenzbedingungen aktzeptiert und mit Install die Xtend-Erweiterung installiert.
Test der Installation mit 'Hello World!'
Zum Testen von Xtend wird eine neue Klasse erstellt, die einfach Hello World!
ausgibt.
Als erstes wird dazu ein Java-Projekt in Eclipse über das Menü mit File->New->Java Project angelegt. Name des Projekts ist XtendHelloWorld
.
Als nächstes selektiert man den Ordner src und öffnet mit dem Kontextmenü den Wizard zum Erstellen von Xtend-Klassen. (Mit cmd+N öffnet sich der Wizard auch.)
In dem Dialog wird nach Xtend gesucht und das passende Template für die Xtend-Datei ausgewählt und mit Next einen Schritt weiter gesprungen.
Dort wird als Package org.hameister.xtend
und als Name der Wert HelloWorld eingetragen und mit Finish bestätigt.
In der generierten Klasse wird noch ein Kompilierungsfehler angezeigt, der dadurch zu Stande kommt, dass die drei notwendigen externen Libraries noch nicht Teil des Klassenpfads sind. Dies läßt sich aber durch ein Klick auf den Fehler beheben.
Anschließend werden folgende Zeilen ergänzt und das Programm gestartet.
package org.hameister.xtend class HelloWorld { def static void main(String[] args) { println("Hello World!") } }
Dies war 'Hello World!' mit Xtend. :-)
Arbeitsweise von Xtend
Xtend arbeitet so, dass in Eclipse eine Xtend-Datei erstellt wird, aus der eine Java-Datei generiert wird. Diese generierten Java-Dateien befinden sich im Projekt in dem Ordner xtend-gen. Diese Java-Dateien werden von Eclipse, wie normaler Quellcode behandelt und beim Übersetzen in Class-Dateien transformiert.
Durch diesen Zwischenschritt, über den Java-Quellcode, ist ein sehr komfortables Debuggen möglich. Außerdem kann man sich so den Java-Code ansehen und ggf. Fehler im eigenen Xtend-Code finden.
Vergleich Java vs. Xtend
Als erstes möchte ich zeigen, dass es mit Xtend möglich ist, kompakteren und verständlicheren Quellcode zu schreiben, als dies mit Java oft möglich ist. In Java ist es häufig so, dass eine Menge Boilerplate Code erstellt werden muss, der den Entwickler nur ablenkt und keinem Mehrwert bringt. Beispielsweise die lästigen Getter und Setter.
Die Klasse Person
Das folgende Codebeispiel zeigt eine Klasse Person
mit einigen Membervariablen und den zugehörigen Gettern und Settern. Auf das Überladen von toString
, hashcode
und equals
habe ich verzichtet. (Die unsinnige JavaDoc fehlt natürlich auch...)
/** Person.java */ package org.hameister.xtend; import java.util.Date; public class Person { private String lastname; private String firstname; private String address; private String zip; private String city; private Date birthdate; public Person(String lastname, String firstname, String address, String zip, String city, Date birthday) { this.lastname = lastname; this.firstname = firstname; this.address = address; this.zip = zip; this.city = city; this.birthdate = birthday; } public String getLastname() { return lastname; } public void setLastname(String lastname) { this.lastname = lastname; } public String getFirstname() { return firstname; } public void setFirstname(String firstname) { this.firstname = firstname; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getZip() { return zip; } public void setZip(String zip) { this.zip = zip; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public Date getBirthdate() { return birthdate; } public void setBirthdate(Date birthdate) { this.birthdate = birthdate; } public String greetPerson(PersonHandlerInterface greeter) { if(greeter != null) { return greeter.getGreeting(firstname+" "+lastname); } //Default greeting return "Hello "+firstname+" "+lastname+"!"; } }
Die gleiche Funktionalität läßt sich folgendermaßen mit Xtend ausdrücken:
/** XtendPerson.xtend */ package org.hameister.xtend import org.eclipse.xtend.lib.Data import java.util.Date @Data class XtendPerson { String lastname String firstname String address String zip String city Date birthdate def String greetPerson(PersonHandlerInterface greeter) { if(greeter != null) { return greeter.getGreeting("") + firstname+" "+lastname+"!" } //Default greeting return "Hello "+firstname+" "+lastname+"!"; } }
Die Getter, Setter und Konstruktor läßt man weg. Diese Methoden werden durch die Annotation @Data
generiert. Auch das Schlüsselwort public
ist nicht notwendig. Alles ist public
, wenn man nichts anderes hinschreibt.
Damit sich die beiden Klasse kompilieren lassen, wird noch das Interface PersonHandlerInterface
benötigt. Dies sieht folgendermaßen aus:
/** PersonHandlerInterface.java */ package org.hameister.xtend; public interface PersonHandlerInterface { public String getGreeting(String greeting); }
Das Interface dient nur dazu später eine Klasse, die dieses Interface implementiert, zu übergeben und die entsprechende Person zu "grüßen".
Die Klasse PersonCreator
Um mit Java eine Menge von Personen zu stellen, könnte man folgendermaßen vorgehen. Er wird eine Art Factory-Klasse erstellt, die eine statische Methode hat, die Objekte vom Typ Person
erstellt.
/** PersonCreator.java */ package org.hameister.xtend; import java.util.ArrayList; import java.util.List; public class PersonCreator { public static List<Person> createPersonList() { List<Person> persons = new ArrayList<Person>(); for(int i=0; i<10;i++) { persons.add(new Person("Duck", "Donald"+i, "", "", "", null)); } return persons; } }
Mit Xtend sieht die Klasse folgendermaßen aus:
/** XtendPersonCreator.xtend */ package org.hameister.xtend import java.util.ArrayList import java.util.List class XtendPersonCreator { def static List<XtendPerson> createPersonList() { val persons = new ArrayList<XtendPerson> for (i : 1 .. 10) { persons.add(new XtendPerson("Duck", "Donald"+i, "", "", "", null)) } return persons } }
Was bei der Xtend-Version auffällt ist, dass Dinge weggelassen werden können. Beispielsweise ist es nach dem Erstellen der ArrayList
nicht notwendig den Typ der Variable persons
anzugeben. Das Schlüsselwort val
ist ausreichend. Auch in der for
-Schleife kann der Datentyp weggelassen werden.
Integration von Xtend-Klassen in eine Java-Klasse
In diesem Abschnitt werden die oben erstellen Java- und Xtend-Klasse in eine Java-Klasse integriert. Da es sich um eine Java-Klasse handelt, kann logischerweise keine Xtend-Syntax verwendet werden, allerdings können die Xtend-Klasse nahtlos in den Java-Code integriert werden.
/** XtendJavaTest.java */ package org.hameister.xtend; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.List; public class XtendJavaTest { public static void main(String[] args) throws ParseException { XtendPerson personXtend = new XtendPerson("Duck", "Donald", "Gänsegasse 1", "1234", "Entenhausen", new SimpleDateFormat("dd.MM.yyyy").parse("31.12.1974")); System.out.println(personXtend.getFirstname()+" "+personXtend.getLastname()+" hat am "+personXtend.getBirthdate()+" Geburtstag!"); System.out.println(personXtend); //Create a list with Persons List<XtendPerson> persons = XtendPersonCreator.createPersonList(); for(XtendPerson p : persons) { System.out.println(p); } Person personJava = new Person("Duck", "Dagobert", "Schwanen-Allee 1", "1234", "Entenhausen", new SimpleDateFormat("dd.MM.yyyy").parse("01.09.1965")); //Default greeting (Java Person) System.out.println(personJava.greetPerson(null)); //Custom greeting (Java Person) System.out.println(personJava.greetPerson(new PersonHandlerInterface() { @Override public String getGreeting(String greeting) { return "Good morning "+greeting+"!"; } })); // Default greeting (Xtend Person) System.out.println(personXtend.greetPerson( null )); // Custom greeting (Xtend Person) System.out.println(personXtend.greetPerson(new PersonHandlerInterface() { @Override public String getGreeting(String greeting) { return "Good morning "; } })); } }
Als erstes wird ein Objekt vom Typ XtendPerson
erstellt. Für dieses werden die Getter und die Methode toString()
getestet. Der Aufruf von toString
erfolgt dabei durch System.out.println
. Anschließend wird ab Zeile 18 eine Liste mit Xtend-Objekten erstellt und ausgegeben.
In Zeile 23 wird ein Java-Objekt Person
erstellt, um das Greeter-Interface zu testen. In Zeile 26 wird der Wert null
übergeben, wodurch der default Gruß ausgegeben wird. Also Hello Dagobert Duck!
.
Ab Zeile 29 wird das PersonHandlerInterface
implementiert und an die Methode greetPerson
des Objekts personJava
übergeben, so dass der Text Good morning Dagobert Duck!
ausgegeben wird.
Für das Xtend-Objekt wird in der darauf folgenden Zeilen das gleiche gemacht. Den Sinn dieser Zeilen, erkennt man, wenn wir im folgenden Abschnitt, die Java-Klassen in Xtend-Quellcode integrieren.
Integration von Java-Klassen in eine Xtend-Klasse
In diesem Abschnitt werden die oben erstellen Java- und Xtend-Klasse in eine Xtend-Klasse integriert. Dadurch kann auch die Xtend-Syntax verwendet werden, wodurch der Quellcode kürzer wird und lesbarer wird.
/** JavaXtendTest.java */ package org.hameister.xtend import java.text.SimpleDateFormat class JavaXtendTest { def static void main(String[] args) { val personXtend = new XtendPerson("Duck", "Donald", "Gänsegasse 1", "1234", "Entenhausen", new SimpleDateFormat("dd.MM.yyyy").parse("31.12.1974")) println(personXtend.getFirstname()+" "+personXtend.getLastname()+" hat am "+personXtend.getBirthdate()+" Geburtstag!") println(personXtend) //Create a list with Persons val persons = XtendPersonCreator::createPersonList(); for(XtendPerson p : persons) { println(p); } val personJava = new Person("Duck", "Dagobert", "Schwanen-Allee 1", "1234", "Entenhausen", new SimpleDateFormat("dd.MM.yyyy").parse("01.09.1965")) // Default greeting (Java Person) println(personJava.greetPerson(null)) // Custom greeting (Java Person) println(personJava.greetPerson(["Good morning "+personJava.firstname+" "+personJava.lastname])) // Default greeting (Xtend Person) println(personXtend.greetPerson( null )) // Custom greeting (Xtend Person) println(personXtend.greetPerson([ "Good morning "])) println("End of processing...") } }
Wie auch schon im vorigen Abschnitt wird erst eine XtendPerson
und anschließend eine Java-Person
erstellt.
Interessant wird es erst, ab Zeile 27, wenn das PersonHandlerInterface
verwendet wird. Da kommen die Lambda-Ausdrücke zum Einsatz. Durch diese kann, sowohl bei den Java-Objekten, als auch bei den Xtend-Objekte eine sehr übersichtliche Schreibweise gewählt werden.
Fazit
Wie schon in der Einleitung gesagt, läßt sich Xtend sehr gut in bestehende Java-Anwendungen integrieren und das Zusammenspiel mit Eclipse funktioniert auch sehr gut. Die Syntax ist, wie bei jeder neuen Sprache, die man lernt, erstmal gewöhnungsbedrüftig. Aber das ist meiner Ansicht nach normal.
Mir hat sehr gut gefallen, dass aus der Xtend-Datei erstmal Java-Quellcode generiert wird, den man sich ansehen kann. Falls also mal Probleme auftreten, kann man sich einfach den Java-Code ansehen und das Problem dort suchen. Durch diesen Zwischenschritt ist auch das Debugging in diese Java-Klassen problemlos möglich.
Ein weiterer Pluspunkt ist, dass nicht ein ganzen "Zoo" von jar-Dateien zum Verwenden von Xtend benötigt wird. Drei Libraries sind ausreichend.
Wie bei allen Spracherweiterungen und/oder neuen Libraries und Frameworks sollte man sich aber gut überlegen, ob es wirklich notwendig ist, sie in ein Projekt zu integrieren. D.h. man sollte einen wirklichen Vorteil durch die Verwendung haben.
Links
Auf folgenden Seiten findet man weitere Informationen zu Xtend: