Java wird erwachsen

Anfang 1996 wurde das JDK 1.0 veröffentlicht. 18 Jahre später, im März 2014, erschien das JDK 8. Das Release mit den bisher vermutlich größten Erweiterungen – was ändert sich?

Java 8 bringt die größten Syntaxveränderungen in der Geschichte von Java mit sich, mindestens aber seit Java 5. Hier wurden Sprachfeatures wie Generics, Annotations, Enums und variable Parameterlisten etabliert, die mittlerweile in den alltäglichen Gebrauch übernommen wurden. Mit Java 8 wird das imperative Sprachkonzept um funktionale Elemente erweitert. Die wichtigsten Neuerungen sind Lambda-Ausdrücke, Streams und Default-Methoden in Interfaces.

Tanz den Lamb(a)da

Lambda-Ausdrücke sind anonyme Funktionen, die eine elegante Formulierung ermöglichen, wo man früher umständliche anonyme Klassen verwenden musste. Ein Beispiel ist die Methode File.listFiles(FilenameFilter ff). Diese nimmt als Parameter einen Filter, um nur bestimmte Dateien zu listen. Der Knackpunkt: Die Filterlogik ist oft recht speziell und kann nicht wiederverwendet werden – d.h., man definiert sie an Ort und Stelle über eine anonyme innere Klasse:

File[] hiddenFiles = new File(".").listFiles(new FilenameFilter() {
   @Override
   public boolean accept(File dir, String name) {
      return new File(dir, name).isHidden();
   }
});

Mit Lambda-Ausdrücken lässt sich dies wesentlich eleganter formulieren:

File[] hiddenFiles = new File(".").listFiles((File f) -> f.isHidden());

Hier wird eine Funktion, die ein File-Objekt auf ein boolean abbildet, definiert und der Methode listFiles übergeben. Diese ist vom Typ Predicate<File>. Der Vorteil: Anstatt die Funktion Lambda-Ausdruck zu formulieren, kann auch eine Referenz auf eine bestehende Methode übergeben werden – dadurch profitiert der Entwickler von einer größeren Klarheit und damit Wartbarkeit:

File[] hiddenFiles = new File(".").listFiles(File::isHidden);

Schwimm mit dem Strom

Streams sind eine natürliche Ergänzung zu den Lambda-Ausdrücken. Sie ermöglichen einen Programmierstil mit Pipes & Filters, wie man ihn von der Unix-Kommandozeile kennt. Bei diesem werden die Eingangsdaten in einen Stream gewandelt, der Stream wird (mit einem Lambda-Ausdruck) gefiltert, wobei das Ergebnis wieder ein Stream ist, der weiterverarbeitet werden kann. Um Listen nach regulären Ausdrücken zu filtern, umzuformatieren, zu sortieren und auszugeben, war bisher eine Menge Code nötig. Mit den Streams lässt sich das elegant und knapp formulieren.

list.stream().filter(s-> s.matches("[a-zA-Z]-[0-9]*-.*"))
             .map(String::toUpperCase)
             .sorted()
             .forEach(System.out::println);

Das Beste: Durch die Parallelisierung der Ausführung mit parallel streams werden alle verfügbaren Prozessorkerne benutzt. Noch nie war Multithreading so einfach. Allerdings verbergen sich hinter der einfachen Facade noch immer einige Tücken: Wenn die Operationen gemeinsame Ressourcen nutzen, kann es zu Race Conditions oder Blockaden kommen.

Default-Methoden

Mit Java 8 werden Default-Methoden in Interfaces eingeführt, d.h., sie können nun Implementierungen für die Methoden enthalten. Bis Java 7 war das nicht möglich und damit waren Probleme der Mehrfachvererbung, wie man sie von C++ und anderen Sprachen kennt, ausgeschlossen. Auf den ersten Blick ist es widersinnig, den lange beworbenen Vorteil zu konterkarieren; die Motivation der Sprachentwickler erschließt sich bei genauerer Betrachtung.

Die Java-APIs sind in den letzten 18 Jahren nicht nur gereift, sondern auch gealtert. Erweiterungen an den APIs der Standardbibliothek sind kaum möglich. Nehmen wir z.B. das Interface java.util.List – es ist millionenfach implementiert, sowohl in der Standard-API als auch in zahllosen Bibliotheken und Anwendungen. Viele Programmierer haben sich in der Vergangenheit sortierbare Listen gewünscht. Eine Methode sort() in das List Interface hinzuzufügen, hätte allerdings bedeutet, dass alle Implementierungen angepasst werden müssen. Damit hätte Oracle den Zorn der Community auf sich gezogen. In der Folge konnten die Interfaces der Standard-API praktisch nicht mehr erweitert werden. Java war Opfer seines eigenen Erfolgs geworden. Die API erstarrte und begann gegenüber neuen Entwicklungen „alt auszusehen“. Default-Methoden sind der Ausweg aus diesem Dilemma.

In freier Wildbahn

Erfahrungsgemäß geht die Adaption eines neuen Java-Releases eher langsam voran – so ist es auch bei Java 8. Im Internetforum Stack Overflow sind bisher nur 1.780 Posts mit „Java 8“ gekennzeichnet; das Tag „Java“ tragen mehr als 700.000 Beiträge. Und auch eine Suche bei Google oder GitHub nach Maven-Projekten, bei denen die neue Java-Version verwendet wird, fördert wenige Treffer zutage. Spätestens, wenn der Support für die älteren SDK-Versionen ausläuft, wird sich dieses Bild allerdings wandeln.

Java 8 kommt langsam aber gewaltig. Mit den neuen Sprachelementen wird die Lernkurve für Neueinsteiger steiler. Ein gedankenloser Umgang mit den funktionalen Elementen oder Default Interfaces kann die Komplexität in Projekten erhöhen. Es ist entscheidend, sich frühzeitig mit den zentralen Änderungen auseinanderzusetzen – denn nur so lassen sich die Vorteile effektiv nutzen sowie Wildwuchs und Anti-Patterns vermeiden.

 

Bildquelle: Shutterstock

Hinterlassen Sie eine Antwort

Ihre E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.