Eclipse Xtend Tutorial

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.

Create New Project

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.

Create New Project

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.)

Create New Project

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.

Create New Project

Dort wird als Package org.hameister.xtend und als Name der Wert HelloWorld eingetragen und mit Finish bestätigt.

Create New Project

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.

Create New Project

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: