Introduzione a Java funzionale

1. Panoramica

In questo tutorial, forniremo una rapida panoramica della libreria Java funzionale insieme ad alcuni esempi.

2. La libreria Java funzionale

La libreria Java funzionale è una libreria open source pensata per facilitare la programmazione funzionale in Java. La libreria fornisce molte astrazioni di programmazione di base e avanzate comunemente utilizzate nella programmazione funzionale.

Gran parte delle funzionalità della libreria ruota attorno all'interfaccia F. Questo F modelli di interfaccia una funzione che riceve un ingresso di tipo A e restituisce un'uscita di tipo B . Tutto questo si basa sul sistema di tipi di Java.

3. Dipendenze di Maven

Innanzitutto, dobbiamo aggiungere le dipendenze richieste al nostro file pom.xml :

 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. Definizione di una funzione

Iniziamo creando una funzione che possiamo utilizzare nei nostri esempi in seguito.

Senza Java funzionale, un metodo di moltiplicazione di base sarebbe simile a:

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

Utilizzando la libreria Java funzionale, possiamo definire questa funzionalità in modo un po 'più elegante:

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

Sopra, vediamo un esempio dell'interfaccia F che accetta un intero come input e restituisce quell'Integer per due come output.

Ecco un altro esempio di una funzione di base che accetta un numero intero come input, ma in questo caso restituisce un valore booleano per indicare se l'input era pari o dispari:

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

5. Applicazione di una funzione

Ora che abbiamo le nostre funzioni in atto, appliciamole a un set di dati.

La libreria Java funzionale fornisce il solito set di tipi per la gestione di dati come elenchi, set, array e mappe. La cosa fondamentale da capire è che questi tipi di dati sono immutabili.

Inoltre, la libreria fornisce utili funzioni per convertire in e da classi Java Collections standard, se necessario.

Nell'esempio seguente, definiremo un elenco di numeri interi e applicheremo ad esso la nostra funzione timesTwo . Chiameremo anche map usando una definizione inline della stessa funzione. Naturalmente, ci aspettiamo che i risultati siano gli stessi:

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)); }

Come possiamo vedere map restituisce un elenco della stessa dimensione in cui il valore di ogni elemento è il valore dell'elenco di input con la funzione applicata. L'elenco di input stesso non cambia.

Ecco un esempio simile utilizzando la nostra funzione isEven :

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)); }

Poiché il metodo map restituisce una lista, possiamo applicare un'altra funzione al suo output. L'ordine in cui invochiamo le nostre funzioni di mappa altera il nostro output risultante:

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)); }

L'output degli elenchi di cui sopra sarà:

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

6. Filtraggio utilizzando una funzione

Un'altra operazione utilizzata di frequente nella programmazione funzionale consiste nel prendere un input e filtrare i dati in base ad alcuni criteri . E come probabilmente hai già intuito, questi criteri di filtraggio sono forniti sotto forma di una funzione. Questa funzione dovrà restituire un valore booleano per indicare se i dati devono essere inclusi o meno nell'output.

Ora, usiamo la nostra funzione isEven per filtrare i numeri dispari da un array di input usando il metodo filter :

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)); }

Un'osservazione interessante è che in questo esempio, abbiamo usato un Array invece di un List come abbiamo usato negli esempi precedenti e la nostra funzione ha funzionato bene. A causa del modo in cui le funzioni vengono astratte ed eseguite, non è necessario che siano consapevoli del metodo utilizzato per raccogliere l'input e l'output.

In questo esempio, abbiamo utilizzato anche la nostra funzione isEven , ma la classe Integer di Java Functional ha anche funzioni standard per confronti numerici di base.

7. Applicazione della logica booleana mediante una funzione

Nella Programmazione Funzionale, usiamo frequentemente logiche come "fallo solo se tutti gli elementi soddisfano una certa condizione", o "fallo solo se almeno un elemento soddisfa una certa condizione".

La libreria Java funzionale ci fornisce scorciatoie per questa logica attraverso il esiste e le Forall metodi:

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))); }

Nell'esempio sopra, abbiamo utilizzato un array di stringhe come input. La chiamata alla funzione fromString convertirà ciascuna delle stringhe dall'array in un elenco di caratteri. A ciascuna di queste liste, abbiamo applicato forall (Characters.isLowerCase) .

Come probabilmente hai intuito, Characters.isLowerCase è una funzione che restituisce true se un carattere è minuscolo. Quindi applicare forall (Characters.isLowerCase) a un elenco di caratteri restituirà true solo se l'intero elenco è composto da caratteri minuscoli, il che a sua volta indica che la stringa originale era tutta minuscola.

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”.

La libreria Java funzionale si riferisce a questa funzionalità come pieghevole .

È necessario specificare una funzione per indicare cosa significa piegare l'elemento. Un esempio di ciò è la funzione Integers.add per mostrare gli interi in un array o è necessario aggiungere un elenco.

In base a ciò che fa la funzione durante la piegatura, il risultato può essere diverso a seconda che si inizi a piegare da destra o da sinistra. Ecco perché la libreria Java funzionale fornisce entrambe le versioni:

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); }

Il primo foldLeft aggiunge semplicemente tutti gli interi. Mentre il secondo applicherà prima un filtro e quindi aggiungerà gli interi rimanenti.

10. Conclusione

Questo articolo è solo una breve introduzione alla libreria Java funzionale.

Come sempre, il codice sorgente completo dell'articolo è disponibile su GitHub.