Einführung in funktionales Java

1. Übersicht

In diesem Tutorial geben wir einen kurzen Überblick über die Functional Java-Bibliothek sowie einige Beispiele.

2. Die funktionale Java-Bibliothek

Die Functional Java-Bibliothek ist eine Open Source-Bibliothek, die die funktionale Programmierung in Java erleichtern soll. Die Bibliothek bietet viele grundlegende und erweiterte Programmierabstraktionen, die üblicherweise in der funktionalen Programmierung verwendet werden.

Ein Großteil der Funktionalität der Bibliothek dreht sich um die F- Schnittstelle. Diese F- Schnittstelle modelliert eine Funktion, die eine Eingabe vom Typ A verwendet und eine Ausgabe vom Typ B zurückgibt . All dies basiert auf Javas eigenem Typsystem.

3. Maven-Abhängigkeiten

Zuerst müssen wir die erforderlichen Abhängigkeiten zu unserer Datei pom.xml hinzufügen :

 org.functionaljava functionaljava 4.8.1   org.functionaljava functionaljava-java8 4.8.1   org.functionaljava functionaljava-quickcheck 4.8.1   org.functionaljava functionaljava-java-core 4.8.1 

4. Funktion definieren

Beginnen wir mit der Erstellung einer Funktion, die wir später in unseren Beispielen verwenden können.

Ohne Functional Java würde eine grundlegende Multiplikationsmethode ungefähr so ​​aussehen:

public static final Integer timesTwoRegular(Integer i) { return i * 2; }

Mit der Functional Java-Bibliothek können wir diese Funktionalität etwas eleganter definieren:

public static final F timesTwo = i -> i * 2;

Oben sehen wir ein Beispiel für die F- Schnittstelle, die eine Ganzzahl als Eingabe verwendet und diese Ganzzahl mal zwei als Ausgabe zurückgibt .

Hier ist ein weiteres Beispiel für eine Grundfunktion, die eine Ganzzahl als Eingabe verwendet, in diesem Fall jedoch einen Booleschen Wert zurückgibt, um anzugeben, ob die Eingabe gerade oder ungerade war:

public static final F isEven = i -> i % 2 == 0;

5. Anwenden einer Funktion

Nachdem wir unsere Funktionen eingerichtet haben, wenden wir sie auf einen Datensatz an.

Die Functional Java-Bibliothek bietet die üblichen Typen zum Verwalten von Daten wie Listen, Gruppen, Arrays und Karten. Der Schlüssel zu erkennen ist, dass diese Datentypen unveränderlich sind.

Darüber hinaus bietet die Bibliothek praktische Funktionen zum Konvertieren in und aus Standard-Java-Sammlungsklassen, falls erforderlich.

Im folgenden Beispiel definieren wir eine Liste von Ganzzahlen und wenden unsere timesTwo- Funktion darauf an. Wir werden map auch mit einer Inline-Definition derselben Funktion aufrufen . Natürlich erwarten wir, dass die Ergebnisse gleich sind:

public void multiplyNumbers_givenIntList_returnTrue() { List fList = List.list(1, 2, 3, 4); List fList1 = fList.map(timesTwo); List fList2 = fList.map(i -> i * 2); assertTrue(fList1.equals(fList2)); }

Wie wir sehen können, gibt map eine Liste derselben Größe zurück, wobei der Wert jedes Elements der Wert der Eingabeliste mit der angewendeten Funktion ist. Die Eingabeliste selbst ändert sich nicht.

Hier ist ein ähnliches Beispiel mit unserer isEven- Funktion:

public void calculateEvenNumbers_givenIntList_returnTrue() { List fList = List.list(3, 4, 5, 6); List evenList = fList.map(isEven); List evenListTrueResult = List.list(false, true, false, true); assertTrue(evenList.equals(evenListTrueResult)); }

Da die Map- Methode eine Liste zurückgibt, können wir eine andere Funktion auf ihre Ausgabe anwenden. Die Reihenfolge, in der wir unsere Kartenfunktionen aufrufen, ändert unsere resultierende Ausgabe:

public void applyMultipleFunctions_givenIntList_returnFalse() { List fList = List.list(1, 2, 3, 4); List fList1 = fList.map(timesTwo).map(plusOne); List fList2 = fList.map(plusOne).map(timesTwo); assertFalse(fList1.equals(fList2)); }

Die Ausgabe der obigen Listen lautet:

List(3,5,7,9) List(4,6,8,10)

6. Filtern mit einer Funktion

Eine weitere häufig verwendete Operation in der funktionalen Programmierung besteht darin , Daten einzugeben und anhand einiger Kriterien herauszufiltern . Und wie Sie wahrscheinlich bereits erraten haben, werden diese Filterkriterien in Form einer Funktion bereitgestellt. Diese Funktion muss einen Booleschen Wert zurückgeben, um anzugeben, ob die Daten in die Ausgabe aufgenommen werden müssen oder nicht.

Verwenden wir nun unsere Funktion isEven , um die ungeraden Zahlen aus einem Eingabearray mithilfe der Filtermethode herauszufiltern :

public void filterList_givenIntList_returnResult() { Array array = Array.array(3, 4, 5, 6); Array filteredArray = array.filter(isEven); Array result = Array.array(4, 6); assertTrue(filteredArray.equals(result)); }

One interesting observation is that in this example, we used an Array instead of a List as we used in previous examples, and our function worked fine. Because of the way functions are abstracted and executed, they do not need to be aware of what method was used to collect the input and output.

In this example, we also used our own isEven function, but Functional Java's own Integer class also has standard functions for basic numerical comparisons.

7. Applying Boolean Logic Using a Function

In Functional Programming, we frequently use logic like “only do this if all elements satisfy some condition”, or “only do this if at least one element satisfies some condition”.

The Functional Java library provides us with shortcuts for this logic through the exists and the forall methods:

public void checkForLowerCase_givenStringArray_returnResult() { Array array = Array.array("Welcome", "To", "baeldung"); assertTrue(array.exists(s -> List.fromString(s).forall(Characters.isLowerCase))); Array array2 = Array.array("Welcome", "To", "Baeldung"); assertFalse(array2.exists(s -> List.fromString(s).forall(Characters.isLowerCase))); assertFalse(array.forall(s -> List.fromString(s).forall(Characters.isLowerCase))); }

In the example above, we used an array of strings as our input. Calling the fromString function will convert each of the strings from the array into a list of characters. To each of those lists, we applied forall(Characters.isLowerCase).

As you probably guessed, Characters.isLowerCase is a function that returns true if a character is lowercase. So applying forall(Characters.isLowerCase) to a list of characters will only return true if the entire list consists of lowercase characters, which in turn then indicates that the original string was all lowercase.

In the first two tests, we used exists because we only wanted to know whether at least one string was lowercase. The third test used forall to verify whether all strings were lowercase.

8. Handling Optional Values With a Function

Handling optional values in code typically requires == null or isNotBlank checks. Java 8 now provides the Optional class to handle these checks more elegantly, and the Functional Java library offers a similar construct to deal with missing data gracefully through its Option class:

public void checkOptions_givenOptions_returnResult() { Option n1 = Option.some(1); Option n2 = Option.some(2); Option n3 = Option.none(); F
    
      function = i -> i % 2 == 0 ? Option.some(i + 100) : Option.none(); Option result1 = n1.bind(function); Option result2 = n2.bind(function); Option result3 = n3.bind(function); assertEquals(Option.none(), result1); assertEquals(Option.some(102), result2); assertEquals(Option.none(), result3); }
    

9. Reducing a Set Using a Function

Finally, we will look at functionality to reduce a set. “Reducing a set” is a fancy way of saying “rolling it up into one value”.

Die Functional Java-Bibliothek bezeichnet diese Funktionalität als Faltung .

Es muss eine Funktion angegeben werden, die angibt, was das Falten des Elements bedeutet. Ein Beispiel hierfür ist die Funktion Integers.add , mit der die Ganzzahlen in einem Array oder einer Liste angezeigt werden, die hinzugefügt werden müssen.

Abhängig davon, was die Funktion beim Falten tut, kann das Ergebnis unterschiedlich sein, je nachdem, ob Sie mit dem Falten von rechts oder links beginnen. Aus diesem Grund bietet die Functional Java-Bibliothek beide Versionen:

public void foldLeft_givenArray_returnResult() { Array intArray = Array.array(17, 44, 67, 2, 22, 80, 1, 27); int sumAll = intArray.foldLeft(Integers.add, 0); assertEquals(260, sumAll); int sumEven = intArray.filter(isEven).foldLeft(Integers.add, 0); assertEquals(148, sumEven); }

Das erste foldLeft fügt einfach alle ganzen Zahlen hinzu. Während die zweite zuerst einen Filter anwendet und dann die verbleibenden ganzen Zahlen hinzufügt.

10. Schlussfolgerung

Dieser Artikel ist nur eine kurze Einführung in die Functional Java-Bibliothek.

Wie immer ist der vollständige Quellcode des Artikels auf GitHub verfügbar.