Ist Java 8 eine funktionale Programmiersprache?
Java 8 wird erst 2014 fertig, aber die erste Testversion ist seit kurzem als Early Access Download verfügbar.
Seit längerer Zeit ist auch bekannt, wo der inhaltliche Fokus der neuen Java-Version ist: Im Project Lambda, das endlich funktionale Features in Java integriert. Damit trägt Oracle vor allem der Tatsache Rechnung, dass moderne Multicore-Architekturen in Java nur sehr umständlich zu Höchstleistungen überredet werden können; dazu mehr in einem zukünftigen Artikel. Hier soll es erstmal um das primäre Feature funktionaler Programmiersprachen gehen: die Unterstützung von Funktionen als Objekte erster Klasse.
Eigentlich war es in Java schon immer möglich, Funktionen als
Objekte zu behandeln. Eine klassische Higher-Order-Funktion wie
map
können wir z.B. so schreiben:
Diese Funktion akzeptiert eine Funktion und eine Liste, wendet die Funktion auf jedes Element der Liste an, und liefert eine Liste der Resultate.
Leider ist das Beispiel noch nicht vollständig, da der Typ Function
nicht bei Java mitgeliefert wird. Hier ist seine Definition:
Die map
-Funktion ist nicht wesentlich schwieriger zu implementieren
als in funktionalen Sprachen. Allerdings wird bei der Benutzung
ein Problem sichtbar, zumindest in Java vor Version 8: Dort gibt es
nämlich keine direkte Notation, um Funktionen-als-Objekte
hinzuschreiben. Stattdessen ist die platzsparendste Methode,
Funktionsobjekte herzustellen, die anonyme innere
Klasse:
Diese Notation ist schon eine Verbesserung gegenüber Java 1.0, wo es noch keine inneren Klassen gab. Für die alltägliche Verwendung in dem Maß, wie in funktionalen Sprachen üblich, ist diese Notation aber viel zu umständlich und noch dazu schwer lesbar. (Und es gibt noch weitere Probleme.) Typische Programmiermuster aus der funktionalen Programmierung sind damit zwar prinzipiell umsetzbar, aber nicht wirklich praktikabel.
Java 8 „Project Lambda“ bietet nun eine kürzere Notation für
Funktionen-als-Objekte über sogenannte Lambda-Ausdrücke, die es
erlauben, den Aufruf von map
drastisch zu verkürzen:
Das ist eine tolle Sache und macht viele Programmiertechniken aus der funktionalen Programmierung in Java nicht nur hoffähig sondern auch praktikabel.
Besonders schön ist, dass die Definition von map
nicht verändert
werden muss, um von den Lambda-Ausdrücken zu profitieren: Der
Java-Compiler transformiert den Lambda-Ausdruck x -> x + 1
automatisch in die anonyme innere Klasse, die wir in Java vor Version
8 noch schreiben müssen. Das macht der Compiler über sogenanntes
Target Typing: Der Kontext eines Lambda-Ausdrucks bestimmt den
genauen Typ, und der Compiler inferiert hieraus den zu generierenden
Code.
Dieses Feature ist allerdings gleichzeitig auch ein Manko, da Lambda-Ausdrücke keine unabhängige Existenz haben. Folgendes geht also z.B. nicht:
Der Compiler beschwert sich mit:
Target type of a lambda conversion must be an interface type.
Oder anders formuliert: Funktionen, die durch Lambdas erzeugt werden,
müssen in Java immer als Typ ein vordefiniertes Interface haben. Es kommen
dafür sogenannte single-method interfaces in Frage, also solche
Interfaces, die genau eine Methode aufweisen. Dies steht im Gegensatz
zu funktionalen Sprachen (genauer gesagt den statisch getypten
funktionalen Sprachen), bei denen Funktionstypen der Form A -> B
schon vorn vorherein eingebaut sind. Das heißt insbesondere, dass
jede Stelligkeit (also jede mögliche Anzahl von Parametern) ein eigenes Interface erfordert: Das Interface
Function
von oben funktioniert nur für einstellige Funktionen. Die Unterscheidung
zwischen primitiven Typen und Referenztypen erfordert außerdem weitere
Überladung. Entsprechend kommt Java 8 mit einem ganzen Zoo speziell
definierter Funktionstypen, zu besichtigen
hier.
Zu den 42 dort aufgeführten Interfaces gehören solche wohlklingende
Namen wie BiConsumer
, ObjDoubleConsumer
oder ToDoubleBiFunction
.
(Da sind noch nicht einmal Funktionen mit mehr als zwei Parametern
dabei.)
Diese vielen spezialisierten Interfaces führen dazu, dass viele Higher-Order-Funktionen immer noch nicht so nützlich sind wie in funktionalen Sprachen. Zum Beispiel ist es leicht möglich, eine Kompositionsfunktion zu schreiben:
Diese funktioniert - wie map
- eben nur für Function
, aber nicht
für die anderen Interfaces für einstellige Funktionen, die in
java.util.function
definiert sind. Das allein sind schon Predicate
, IntPredicate
,
UnaryOperator
, DoublePredicate
, DoubleToIntFunction
,
DoubleUnaryOperator
, IntToDoubleFunction
, IntToLongFunction
,
IntUnaryOperator
, LongPredicate
,LongToDoubleFunction
,
LongToIntFunction
, LongUnaryOperator
, ToDubleFunction
,
ToIntFunction
und ToLongFunction
.
Mit anderen Worten: Es gibt in Java 8 nun Lambda-Ausdrücke und Funktionsobjekte, aber keine Funktionstypen - die Antwort auf die Frage im Titel ist damit ein klares „Jein“.
Diese Einschränkung ist für funktionale Programmierer bitter, aus Sicht der Java-Macher allerdings verständlich: Sie wollen Java weiterentwickeln, ohne den Kern zu verändern. Die Einführung richtiger Funktionstypen würde allerdings massive Änderungen in der JVM erfordern, die Oracle bisher noch scheut. Hoffen wir, dass es trotzdem eines Tages passiert.