Lambda-Ausdrücke und Methoden-Referenzen: Vergleich von Java 7 mit Java 8

von Hubert Schmid vom 2014-02-23

Es gibt zahlreiche Leute, die sich kritisch zu den mit Java 8 eingeführten Lambda-Ausdrücken und Methoden-Referenzen äußern. Dadurch würde sich die Komplexität der Sprache erhöhen und sich ihr Erlernen erschweren. Interessanterweise stammen viele dieser Aussagen von Leuten, die vor zehn Jahren noch auf der anderen Seite standen und sich für den Einsatz von Generics ausgesprochen haben. Doch das ist eine andere Geschichte.

Man sollte sich stets klar machen: Die Welt um uns herum ändert sich. Dadurch ändert sich auch die Erwartungshaltung an die Softwareentwicklung, und Programmiersprachen entwickeln sich weiter, um diesen Prozess zu unterstützen. Schließlich gibt es einen guten Grund, warum wir heute üblicherweise nicht mehr in Algol 60 programmieren.

Dass der übermäßige Einsatz von Lambda-Ausdrücken und Methoden-Referenzen kontraproduktiv ist, steht außer Frage – genauso wie bei den meisten anderen Sprachmerkmalen. Das sollte einem jedoch nicht den Blick auf die Vorteile verstellen, die sich durch die neuen Möglichkeiten ergeben. Beispiele können dabei helfen, die Vorteile von Java 8 gegenüber Java 7 zu erkennen.

filter

Die folgende Methode bestimmt zu einem Department alle Vollzeit-Mitarbeiter und gibt die Referenzen als Liste zurück. Daran ist eigentlich nicht Besonderes. Es handelt sich vielmehr um eine typische Methode mit zwei ineinander verschachtelten Kontrollstrukturen, so wie sie viele Java-Entwickler jeden Tag mehrfach schreiben.

public List<Employee> getFullTimeEmployees(Department department) { List<Employee> fullTimeEmployees = new ArrayList<>(); for (Employee employee : department.getEmployees()) { if (employee.isFullTime()) { fullTimeEmployees.add(employee); } } return fullTimeEmployees; }

Doch obwohl es sich um ein sehr einfaches Beispiel handelt, ist die Implementierung vergleichsweise schwierig zu erfassen. Mit Lambda-Ausdrücken und Methoden-Referenzen lassen sich solche Aufgaben dagegen natürlicher formulieren. Das folgende Listing zeigt eine Implementierung mit den Features von Java 8.

public List<Employee> getFullTimeEmployees(Department department) { return department.getEmployees().stream() .filter(Employee::isFullTime) // method reference .collect(toList()); }

Gerade im direkten Vergleich zeigt sich der wesentliche Unterschied: Die zweite Implementierung ist nicht nur wesentlich kürzer und direkter – sondern sie beschreibt auch eher das Ziel wohingegen die erste Implementierung eher den Weg beschreibt.

groupingBy

Im zweiten Beispiel werden Mitarbeiter nach ihrer Abteilung gruppiert und das Ergebnis als Map zurückgeliefert. Auch hierbei handelt es sich um eine ganz typische Aufgabe. Dieses Mal beginne ich jedoch mit der Implementierung in Java 8.

public Map<Department, List<Employee>> groupByDepartment(List<Employee> employees) { return employees.stream() .collect(groupingBy(Employee::getDepartment, toList())); }

Wie bereits im vorherigen Beispiel handelt es sich um einen Einzeiler (genauer um eine einzelne Anweisung). Und ebenfalls wie im vorherigen Beispiel klingt die Aufgabe so, als wäre sie auch in Java 7 trivial zu lösen. Doch dem ist keineswegs so, was sich bei genauerer Betrachtung zeigt. Die folgende Implementierung basiert auf den Standard-Collections von Java 7 und ist so komplex, dass man sie bereits wieder in zwei Methoden aufteilen könnte. Der direkte Vergleich zeigt wiederum, welches Potential Lambda-Ausdrücke und Methoden-Referenzen bieten.

public Map<Department, List<Employee>> groupByDepartment(List<Employee> employees) { Map<Department, List<Employee>> departments = new HashMap<>(); for (Employee employee : employees) { List<Employee> group = departments.get(employee.getDepartment()); if (group == null) { group = new ArrayList<>(); departments.put(employee.getDepartment(), group); } group.add(employee); } return departments; }

Die beiden Beispiele sollten nicht den falschen Eindruck erwecken, dass Lambda-Ausdrücke und Methoden-Referenzen grundsätzlich den Code auf einen Bruchteil reduzieren. Die Vorteile sollte man auch gar nicht an dieser Stelle suchen. Viel wichtiger ist nämlich die Lesbarkeit und Wartbarkeit, die sich aus der natürlichen und direkten Implementierung ergibt.