Functional Interfaces in Java8

Unter dem Titel “Es muss nicht immer Scala sein – Elegante Programmierung in Java” habe ich im Rahmen der diesjährigen Firmenmesse der Uni Kassel einen kurzen Einführungsvortrag in Funktionale Programmierung gegeben. Wegen der knappen Zeit habe ich mich auf einen Einblick in Functional Interfaces beschränkt, den ich hier noch einmal aufarbeiten möchte.

Funktionale Programmierung?

Die Idee der Funktionalen Programmierung ist älter als der elektronische Computer denn sie basiert auf den Grundlagen der Mathematik und findet sich im Prinzip auch in den ersten mechanischen Rechenmaschinen wie der von Philipp Matthäus Hahn aus dem Jahr 1770. Sprachen wie LISP oder Haskell gibt es schon seit Jahrzehnten aber ihre Verbreitung war im Großen und Ganzen auf das akademische Umfeld beschränkt. Die starke mathematische Orientierung hat viele Entwickler abgeschreckt weshalb imperative Sprachen immer noch den Markt dominieren.

Doch seit ein paar Jahren rollt Scala auf dem JVM das Feld von hinten auf. Funktionale Programmierung bietet (neben manch anderen) den Vorteil, dass sich die Programme sehr gut parallelisieren lassen und damit viel besser dazu geeignet sind, die zahlreichen Rechenkerne moderner PC auszunutzen. Wie kommt das?

Der Hauptgrund hierfür ist die starke Lokalität funktionaler Software. Statt globale Variablen oder Objekte zu modifizieren arbeiten funktionale Programme mit unveränderlichen Werten. Das bedeutet zum Beispiel, dass ich ein Objekt, dass ich einer Methode übergebe, nicht verändere sondern ein neues Objekt zurück gebe, dass die Änderung widerspiegelt. Zum anderen sind funktionale Methoden (weitgehend) frei von Seiteneffekten; sie arbeiten nur mit lokalen Werten. Das ermöglicht es, Funktionen, deren Ergebnis nicht von dem einer anderen abhängt, in beliebiger Reihenfolge oder gleichzeitig auszuführen. Zudem kann man solche Funktionen sehr schön kombinieren um wie mit Lego aus einfachen funktionalen Bausteinen komplexe System kreieren. Auch so genannte “Funktionen höherer Ordnung”, die Funktionen verarbeiten oder erzeugen gehören in der Funktionalen Programmierung zum Standard und ermöglichen es sehr eleganten Code zu schreiben.

Müssen wir jetzt alle Scala lernen? Nein 🙂 Mit Version 8 wurde Java um ein paar wundervolle Features erweitert, welche die Kombination von Objekt-Orientierter Programmierung und Funktionaler Programmierung erlauben. Ein zentrales Element davon sind Functional Interfaces.

Functional Interfaces

In Java 8 wurden die Interfaces stark erweitert. Anders als in den früheren Sprachversionen können Interfaces fertig implementierte statische Methoden haben, die von den implementierenden Klassen nicht überladen werden können. Ganz neu mit Version 8 gibt es Default-Methoden, die ebenfalls implementiert sind aber überladen werden können. Abstrakte Klassen werden damit eigentlich überflüssig.

Functional Interfaces sind im Prinzip ganz normale Interfaces, die genau eine abstrakte Methode haben sowie beliebig viele default und static Methoden. Man spricht daher auch von SAM Interfaces: Single Abstract Method. Das package java.util.function enthält bereits eine umfangreiche Auswahl an Functional Interfaces für (fast) jeden Zweck. Natürlich kann man auch eigene Interfaces definieren; dazu später mehr.

Funktionen als Variablen

Werfen wir doch einen Blick auf das Interface Function. Function<T,R> repräsentiert – Überraschung! – eine Funktion die einen Wert vom Typ T annimmt und einen Wert vom Typ R zurück gibt. Das Interface enthält die vier Methoden default andThen, default compose , static identity  und  apply. Im folgenden Beispiel ist Function ganz klassisch als anonyme Klasse implementiert so wie es auch schon in Java 7 möglich wäre, wobei wir uns natürlich die drei fertigen Methoden sparen dürfen.

public class Scratchpad {

    private Function<String, String> stringFunction;

    public Scratchpad(){
        this.stringFunction = new Function<String, String>() {
            @Override
            public String apply(String s) {
                return s.replaceAll("X", "");
            }
        };    
    }

    public String doSomething (String string){
        return stringFunction.apply(string);
    }
}

Das ist natürlich nicht das was ich hier zeigen will! Statt dessen weise ich dem Interface lieber eine Funktionsreferenz zu.

public class Scratchpad {

    private Function<String, String> stringFunction;

    public Scratchpad(){
        this.stringFunction = Scratchpad::removeX;
    }

    public String doSomething (String string){
        return stringFunction.apply(string);
    }

    private static String removeX(String string) {
        return string.replaceAll("X", "");
    }
}

Der Aufruf von doSomething() führt zu einem Aufruf von apply auf dem Interface. Der Compiler wiederum hat removeX an die einzige abstrakte Methode im Interface (apply) gebunden und der Aufruf wird weiter gereicht. Das ist schnell ausprobiert:

public class Demo {

    private static final String string = "XYZ XYZ";

    public static void main(String[] args){
        Scratchpad scratchpad = new Scratchpad();
        System.out.println(scratchpad.doSomething(string));
    }
}

Das Ergebnis ist (zum Glück) YZ YZ.

Vorsicht Technik!

In diesem Abschnitt schauen wir kurz unter die Motorhaube der JVM. Wem das zu low level ist: Einfach überspringen.

Da mich mein Kollege Malte danach gefragt hat: Der Compiler erzeugt – anders als bei der anonymen Klasse im ersten Beispiel – im Übrigen kein class file. Ein Blick auf den Bytecode mit javap -c -v Scratchpad.class zeigt was passiert:

public de.flaviait.strings.Scratchpad();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: invokedynamic #2,  0              // InvokeDynamic #0:apply:()Ljava/util/function/Function;
        10: putfield      #3                  // Field stringFunction:Ljava/util/function/Function;
        13: return

  public java.lang.String doSomething(java.lang.String);
    descriptor: (Ljava/lang/String;)Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: getfield      #3                  // Field stringFunction:Ljava/util/function/Function;
         4: aload_1
         5: invokeinterface #4,  2            // InterfaceMethod java/util/function/Function.apply:(Ljava/lang/Object;)Ljava/lang/Object;
        10: checkcast     #5                  // class java/lang/String
        13: areturn

Im Konstruktor wird über invokedynamic die Interface Methode apply gebunden und per putfield stringFunction zugewiesen. In doSomething wiederum wird die Variable stringFunction via getfield geladen und dann mittels invokeinterface die Methode apply direkt aufgerufen.

Funktionen als Parameter

Alles, was ich einer Variablen zuweisen kann, kann ich auch als Methodenparameter verwenden. Das gilt natürlich auch für Functional Interfaces. Um das zu demonstrieren habe ich Scratchpad und Demo ein wenig erweitert.

public class Scratchpad {

    private Function<String, String> stringFunction;

    public Scratchpad(){
        this.stringFunction = Scratchpad::removeX;
    }

    public String doSomething (String string){
        return stringFunction.apply(string);
    }

    private static String removeX(String string) {
        return string.replaceAll("X", "");
    }

    public void setStringFunction(Function<String, String> theFunction) {
        this.stringFunction = theFunction;
    }
}


public class Demo {

    private static final String string = "XYZ XYZ";

    public static void main(String[] args){
        Scratchpad scratchpad = new Scratchpad();
        System.out.println(scratchpad.doSomething(string));

        scratchpad.setStringFunction(Demo::removeY);
        System.out.println(scratchpad.doSomething(string));
    }

    private static String removeY(String string) {
        return string.replaceAll("Y", "");
    }
}

Das ist jetzt nicht so weit weg von dem, was ich gerade eben demonstriert habe und daher ist es auch nicht allzu überraschend, dass das Ergebnis YZ YZ XZ XZ  ist. Obwohl..? Zu beachten ist, dass removeY als private deklariert ist! Trotzdem kann die Methode innerhalb von Scratchpad aufgerufen werden, da die Bindung an Scratchpad.stringFunction die Methode indirekt in den Scope von Scratchpad hievt. Ein direkter Aufruf von Demo.removeY bleibt natürlich weiterhin verboten.

Funktionen als Rückgabewerte

Soweit sind die Beispiele gar nicht so verschieden von dem was man von anonymen Klassen kennt. Doch was, wenn ich einen anderen Buchstaben ersetzen möchte? “Z”, zum Beispiel? Dann wäre es ja sehr praktisch, wenn ich eine Funktion hätte, die mir die passende Funktion maßschneidert…

public class Replacer {
    public static Function<String, String> createReplacingFunction(String replaceMe, String withThis) {
        return (String in) -> {
            return in.replaceAll(replaceMe, withThis);
        };
    }
}

oder noch knackiger

public class Replacer {
    public static Function<String, String> createReplacingFunction(String replaceMe, String withThis) {
        return in -> in.replaceAll(replaceMe, withThis);
    }
}

createReplacingFunction ist eine Funktion höherer Ordnung, da sie keinen Wert sondern eine Funktion zurück gibt. Der Körper der Methode besteht hier aus einem einzelnen Lambda-Ausdruck der über den Scope der umgebenden Methode schließt. Das bedeutet, dass die Variablen replaceMe und withThis innerhalb der Lambda-Funktion zur Verfügung stehen ohne sie explizit zu übergeben. Die Werte der Variablen werden dabei innerhalb der erzeugten Funktion gehalten.

public class Demo {

    private static final String string = "XYZ XYZ";

    public static void main(String[] args){
        Scratchpad scratchpad = new Scratchpad();
        System.out.println(scratchpad.doSomething(string));

        scratchpad.setStringFunction(Demo::removeY);
        System.out.println(scratchpad.doSomething(string));

        Function<String, String> removeZ = Replacer.createReplacingFunction("Z", "");
        scratchpad.setStringFunction(removeZ);
        System.out.println(scratchpad.doSomething(string));
    }

    private static String removeY(String string) {
        return string.replaceAll("Y", "");
    }
}

Und? Funktioniert der Kram denn auch wirklich? Trommelwirbel, Programm starten… YZ YZ XZ XZ XY XY Tadaa! Läuft.

Die Möglichkeiten, die sich hierdurch ergeben, sind beeindruckend. Mittels Function::compose  und Function::andThen  lassen sich beispielsweise zwei Funktionen zu einer zusammenfassen. Kombiniert man das mit der Option, mittels Lamdba-Ausdrücken und Closures dynamisch zur Laufzeit nach Bedarf neue Funktionen zu generieren, kann man damit sehr komplexe Szenarien realisieren.

Checked Exceptions vs. Functional Interfaces

Bei all der Freude über Functional Interfaces gibt es leider einen Wermutstropfen: Checked Exceptions. Der Zwang, Checked Exceptions zu fangen oder explizit im Methodekopf zu deklarieren passt so gar nicht zu den dynamischen, modularen Möglichkeiten, die sich durch Funktionen höherer Ordnung eröffnen. Auf http://codingjunkie.net/functional-iterface-exceptions/ habe ich eine recht elegante Lösung gefunden, die ich zum Abschluss kurz vorstellen möchte. Gleichzeit erfülle ich mein Versprechen vom Anfang des Artikels und zeige, wie man ein eigenes Functional Interface definiert.

@FunctionalInterface
public interface ThrowingFunction<T, R> extends Function<T, R> {

    @Override
    default R apply(T t) {
        try {
            return applyThrows(t);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    R applyThrows(T t) throws Exception;
}

Die Annotation @FunctionalInterface  ist optional. Hierdurch stellt der Compiler sicher, dass es sich wirklich um ein SAM Interface handelt. Das neue Interface implementiert apply  als default  Methode und definiert eine neue abstrakte Methode applyThrows . Wird apply  wie auf einer herkömmlichen Function  aufgerufen, fängt die Methode jede Exception  und verpackt diese in einer RuntimeException . Das ist nicht besonders schön doch nur so lassen sich die Funktionen frei miteinander kombinieren und in den neuen Streams verwenden.

Weiterführendes

Wer jetzt so richtig Lust bekommen hat sich mit Functional Interfaces und Lambda-Ausdrück zu beschäftigen, dem empfehle ich das Buch “Functional Programming in Java” von Venkat Subramaniam.  Und natürlich gibt es im Netz viele Beispiele und Tutorial zu dem Thema. Ich selbst werde in der nächsten Zeit noch das Beispiel zum Composition Pattern mit Functions bloggen. Bis dahin viel Spaß beim Nachprogrammieren und Ausprobieren!

Teilen Sie diesen Beitrag

Das könnte dich auch interessieren …

Schreibe einen Kommentar

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

Durch die weitere Nutzung der Seite stimmen Sie der Verwendung von Cookies zu. Weitere Informationen

Die Cookie-Einstellungen auf dieser Website sind auf "Cookies zulassen" eingestellt, um das beste Surferlebnis zu ermöglichen. Wenn du diese Website ohne Änderung der Cookie-Einstellungen verwendest oder auf "Akzeptieren" klickst, erklärst du sich damit einverstanden.

Schließen