Java 8 hat eine besonders interessante Funktionalität mitgebracht: Lambdas. Ein Lambda ist eine anonyme Funktion und kann etwa als Parameter einer Methode über- oder von einer Methode zurückgegeben werden.
Vor Java 8 hat man üblicherweise eine Klasse erstellt wenn man irgendein Stück Code verwenden wollte. Dies ist aber nicht immer nötig und generiert unnötigen Boilerplate Code.
Zu Lambdas komme ich aber ein anderes Mal. Jetzt geht es um die Functional Interfaces.
Kurze Übersicht
Functional Interfaces:
- Consumer = Akzeptiert ein Argument und gibt nichts zurück
- BiConsumer = Akzeptiert zwei Argumente, etwa von einer Hashmap, und gibt nichts zurück
- Supplier = Will kein Argument, aber gibt eines zurück
- Predicate = Gibt true oder false zurück
- Operator = Will den gleichen Typ als Input und Output
Wie sehen Functional Interfaces aus?
Ein Functional Interface hat üblicherweise eine @FunctionalInterface Annotation. Der Trick bei einem Functional Interface ist, dass diese eine einige abstrakte Methode besitzt. Die Implementation dieser Methode kann dann als Lambda geschehen.
Die neuen „Default“ Methoden, die auch mit Java 8 eingeführt wurden, sind übrigens nicht abstract und zählen daher auch nicht zu den Functional Interfaces.
Das Consumer Interface
Das Interface, was wohl am meisten verwendet wird, ist das Consumer Interface. Ein Consumer akzeptiert ein Argument und gibt nichts zurück. Mit dem Argument wird irgendetwas angestellt.
Zum Beispiel können wir eine Liste von Namen erstellen und dann jeden mit „Hallo“ begrüssen. Die Liste kann ganz einfach mit „Arrays.asList()“ gemacht werden (achtung: Das Arrays hat ein grosses ‚A‘).
List<String> namen = Arrays.asList("Hans", "Wurst", "Gerald"); namen.forEach(name -> System.out.println("Hallo " + name");
Bei der Methode forEach sehen wir schön, dass ein Consumer benötigt wird, welcher einen Parameter braucht, in diesem Fall einen String.
Output: Hallo Hans Hallo Wurst Hallo Gerald
Wenn wir die Klasse „java.util.function.Consumer“ öffnen sieht man am Anfang die Annotation @FunctionalInterface. Die abstrakte Methode ist in diesem Fall „accept“, da wir aber ein obercooles Lambda mit dem -> Pfeil verwendet haben, müssen wir diesen Namen gar nicht kennen.
PS: Die Angabe zu „references | implementation“ ist übrigens vom Code Mining, das gehört nicht zu der Klasse, siehe Code Mining in Eclipse Photon
Das BiConsumer Interface
Ein BiConsumer ist ähnlich wie der Consumer, nur dass er zwei Argumente annimmt, etwa von einer HashMap. Wieder kann man bei der forEach Methode schauen, was benötigt wird, und sieht, dass die Methode ein BiConsumer benötigt.
Map<String, Integer> personen = new HashMap<>(); personen.put("Hans", 20); personen.put("Wurst", 22); personen.put("Gerald", 34); personen.forEach((name, alter) -> System.out.println(name + " ist " + alter + " Jahre alt")); Output: Wurst ist 22 Jahre alt Gerald ist 34 Jahre alt Hans ist 20 Jahre alt
Das Supplier Interface
Der Supplier ist das Gegenteil vom Consumer und absolut selbstlos. Er will überhaupt keinen Parameter, liefert aber dennoch einen Wert zurück.
Das Interface sieht so aus:
Es braucht in der Methode get() keinen Parameter, gibt aber irgendetwas zurück.
Das einfachste Beispiel erstellt einfach einen Supplier, der irgendetwas zurückgibt, etwa in einem Lambda. Nicht vergessen, beim Supplier den Typ mit eckigen Klammern mitzugeben.
Supplier<String> halloSupplier = () -> new String("Hallo"); String halloString = halloSupplier.get();
Das Predicate Interface
Ein Predicate ist eine Funktion, die Inputwerte erhält und nach einer gewissen Logik ein true oder false zurückgeben, also einen Boolean.
Zum Beispiel kann man prüfen, ob ein Name mit „W“ anfängt.
List<String> personen = Arrays.asList("Hans", "Wurst", "Gerald"); List<String> namenMitW = namen.stream() .filter(name -> name.startsWith("W")).collect(Collectors.toList()); namenMitW.forEach(name -> System.out.println(name)); Output: Wurst
Das Operator Interface
Das Operator Interface ist eine spezielle Funktion, die den gleichen Typ als Input wie auch als Output hat.
Die Methode „replaceAll“ etwa möchte einen UnaryOperator haben.
Was übrigens auch neu ist, ist der Zugriff auf bestimmte Methoden mit dem Doppelpunkt, wie im Beispiel mit String::toUpperCase. (Wenn man in Eclipse „String::“ eingibt und dann CTRL + Leertaste drückt kommt eine Liste von Methoden und man kann einfach eine auswählen)
List<String> personen = Arrays.asList("Hans", "Wurst", "Gerald"); namen.replaceAll(String::toUpperCase); Output: HANS WURST GERALD
Echt genial und gar nicht mal so kompliziert, wenn man mal ein paar einfache Beispiele gesehen hat, oder?