Java 8: Anonyme Funktionen, Lambdas und Closures

von Hubert Schmid vom 2013-11-17

In wenigen Monaten wird die kommende Version von Java veröffentlicht. Dominiert wird Java 8 von der Einführung anonymer Funktionen. Dabei handelt es sich wohl um die bedeutendste Erweiterung seit Java 5. Das ist ein guter Grund sich wieder einmal mit dem Thema auseinanderzusetzen.

Die erste Hürde liegt in der babylonischen Sprachverwirrung: Handelt es sich nun um anonyme Funktionen, Lambda-Funktionen, Lambda-Ausdrücke oder Closures? Die Terminologie ist tatsächlich unklar. Doch davon sollte man sich nicht irritieren lassen. Sehr pragmatisch kann man sich dem Thema nähern, indem man sie als Kurzform für anonyme Klassen mit nur einer Methode betrachtet. Über diesen Ansatz motiviere ich die Erweiterung mit einem ausführlicheren Beispiel.

Beispiel

Die folgende Klasse realisiert basierend auf der Swing-Bibliothek eine einfache Digital-Uhr. In den beiden Methoden main und scheduleOnEventQueue finden sich insgesamt vier anonyme Klassen: Eine Unterklasse von JFrame, eine Unterklasse von JButton, sowie zwei Implementierungen der Schnittstelle Runnable. Im Folgenden geht es um die beiden Letzteren.

public class Clock { public static void main(String... args) { new JFrame("Clock") {{ setDefaultCloseOperation(EXIT_ON_CLOSE); getContentPane().add(new JButton(format(millis())) {{ scheduleOnEventQueue(1000, new Runnable() { public void run() { setText(format(millis())); } }); }}); pack(); }}.setVisible(true); } static void scheduleOnEventQueue(long interval, Runnable runnable) { scheduleOnNewThread(interval, new Runnable() { public void run() { EventQueue.invokeLater(runnable); } }); } static void scheduleOnNewThread(long interval, Runnable runnable) {/*...*/} static long millis() { return System.currentTimeMillis(); } static String format(long millis) { return String.format("%TT", millis); } }

Das Runnable in der main-Methode wird für die Aktualisierung der Anzeige verwendet. Das Runnable in der Methode scheduleOnEventQueue ist notwendig, um die periodische Ausführung im sogenannten Event-Dispatcher-Thread durchzuführen, wohingegen die Ausführung aus einem separaten Thread angestoßen wird. Anonyme Klassen werden in Swing-Anwendungen vergleichsweise häufig auf diese Weise eingesetzt.

Runnable

Die Definition der Schnittstelle Runnable ist im folgenden Listing zu sehen. Entscheidend ist die Deklaration genau einer Methode. Solche Schnittstellen werden seit Java 8 als functional Interfaces bezeichnet. Optional können sie noch mit der Annotation @FunctionalInterface ausgezeichnet werden.

public interface Runnable { void run(); }

Zurück zum Beispiel: Die Definition der beiden anonymen Klassen enthält sehr viel Boilerplate. Da es sich bei Runnable allerdings um ein functional Interface handelt, kann man die beiden anonymen Klassen durch anonyme Funktionen ersetzen. Im folgenden Listing wird dafür die Form () -> expression verwendet, wobei das Klammerpaar für die leere Argumentliste der run-Methode steht. Auf diese Weise wird die Implementierung acht Zeilen kürzer.

public static void main(String... args) { new JFrame("Clock") {{ setDefaultCloseOperation(EXIT_ON_CLOSE); getContentPane().add(new JButton(format(millis())) {{ scheduleOnEventQueue(1000, () -> setText(format(millis()))); }}); pack(); }}.setVisible(true); } static void scheduleOnEventQueue(long interval, Runnable runnable) { scheduleOnNewThread(interval, () -> EventQueue.invokeLater(runnable)); }

Supplier

Runnable ist das einfachste functional Interface, da die run-Methode weder Parameter hat noch einen Wert zurückgibt. Anonyme Funktionen funktionieren jedoch genauso für Methoden mit Parametern und Rückgabetypen. Zunächst betrachte ich Letzteres, und Java 8 sieht dafür die Schnittstelle Supplier vor, die vereinfacht wie folgt aussieht:

public interface Supplier<T> { T get(); }

Die folgende anonyme Klasse implementiert die Schnittstelle Supplier und ersetzt zu Demonstrationszwecken im obigen Beispiel die beiden Methoden millis und format.

Supplier<String> time = new Supplier<String>() { public String get() { return String.format("%TT", System.currentTimeMillis()); } };

Einfacher und mit weniger Boilerplate lässt sich die Schnittstelle durch eine anonyme Funktion realisieren. Die Syntax ist dabei identisch zu den obigen Beispiel, und das Ergebnis des Ausdrucks wird zum Rückgabewert der get-Methode.

Supplier<String> time = () -> String.format("%TT", System.currentTimeMillis());

ActionListener

Es fehlt noch ein Beispiel für Methoden-Parameter. Im obigen Beispiel bietet sich dafür ein ActionListener an, den ich an den JButton hänge. Die Schnittstelle des functional Interface sieht vereinfacht wie folgt aus:

public interface ActionListener { void actionPerformed(ActionEvent e); }

Die folgende anonyme Klasse implementiert den ActionListener und gibt das ActionEvent auf die Konsole aus. Die Form mit anonymer Funktion ist darunter zu sehen. Der Name des Parameters steht an Stelle des Klammerpaars und der Parametertyp wird aus dem Kontext abgeleitet.

addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println(e); } }); addActionListener(e -> System.out.println(e));

Die anonyme Funktion verkürzt die Implementierung auf das Wesentliche. Der Boilerplate der anonymen Klassen entfällt, und die Klassen ActionListener und ActionEvent werden weder importiert noch referenziert. Selbstredend können anonyme Funktionen auch mehrere Parameter besitzen, doch für dieses Beispiel soll es zunächst mal reichen.

Ausblick

Ich habe versucht, anonyme Funktionen als Kurzform für anonyme Klassen zu motivieren – sozusagen als syntaktischen Zucker. Einerseits macht es die Einführung für Entwickler mit Java-Kenntnissen sehr einfach, andererseits besteht jedoch die Gefahr, anonyme Funktionen nur auf diese Weise zu sehen.

Zusätzlich zum technischen Verständnis muss auch ein fachliches Umdenken stattfinden. Viele Java-Entwickler sind in der Vergangenheit gelehrt worden, objektorientiert zu denken. Nun kann diese Sicht den effektiven Einsatz anonymer Funktionen behindern.

Schaut man sich die Änderungen an der Standardbibliothek von Java 8 an, so wird man feststellen, dass sich die Unterstützung anonymer Funktionen auf Bereiche fokussiert, die kaum etwas mit klassischer Objektorientierung zu tun haben. Doch darauf werde ich ein anderes Mal eingehen.