Introduction à Java fonctionnel

1. Vue d'ensemble

Dans ce didacticiel, nous fournirons un aperçu rapide de la bibliothèque Functional Java avec quelques exemples.

2. La bibliothèque Java fonctionnelle

La bibliothèque Functional Java est une bibliothèque open source destinée à faciliter la programmation fonctionnelle en Java. La bibliothèque fournit de nombreuses abstractions de programmation de base et avancées couramment utilisées dans la programmation fonctionnelle.

Une grande partie des fonctionnalités de la bibliothèque tourne autour de l' interface F. Cette F modèles d'interface d'une fonction qui prend une entrée de type A et renvoie une sortie de type B . Tout cela est construit sur le système de type de Java.

3. Dépendances de Maven

Tout d'abord, nous devons ajouter les dépendances requises à notre fichier 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. Définition d'une fonction

Commençons par créer une fonction que nous pourrons utiliser dans nos exemples plus tard.

Sans Java fonctionnel, une méthode de multiplication de base ressemblerait à ceci:

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

En utilisant la bibliothèque Functional Java, nous pouvons définir cette fonctionnalité un peu plus élégamment:

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

Ci-dessus, nous voyons un exemple de l' interface F qui prend un entier en entrée et renvoie cet entier multiplié par deux en sortie.

Voici un autre exemple de fonction de base qui prend un entier comme entrée, mais dans ce cas, renvoie un booléen pour indiquer si l'entrée était paire ou impaire:

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

5. Application d'une fonction

Maintenant que nos fonctions sont en place, appliquons-les à un ensemble de données.

La bibliothèque Functional Java fournit l'ensemble habituel de types pour gérer des données telles que des listes, des ensembles, des tableaux et des cartes. La chose clé à réaliser est que ces types de données sont immuables.

En outre, la bibliothèque fournit des fonctions pratiques pour convertir vers et à partir des classes de collections Java standard si nécessaire.

Dans l'exemple ci-dessous, nous allons définir une liste d'entiers et y appliquer notre fonction timesTwo . Nous appellerons également map en utilisant une définition en ligne de la même fonction. Bien sûr, nous nous attendons à ce que les résultats soient les mêmes:

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

Comme nous pouvons le voir, map renvoie une liste de même taille où la valeur de chaque élément est la valeur de la liste d'entrée avec la fonction appliquée. La liste d'entrée elle-même ne change pas.

Voici un exemple similaire utilisant notre fonction 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)); }

Puisque la méthode map renvoie une liste, nous pouvons appliquer une autre fonction à sa sortie. L'ordre dans lequel nous invoquons nos fonctions de carte modifie notre sortie résultante:

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

La sortie des listes ci-dessus sera:

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

6. Filtrage à l'aide d'une fonction

Une autre opération fréquemment utilisée dans la programmation fonctionnelle consiste à prendre une entrée et à filtrer les données en fonction de certains critères . Et comme vous l'avez probablement déjà deviné, ces critères de filtrage sont fournis sous la forme d'une fonction. Cette fonction devra renvoyer un booléen pour indiquer si les données doivent être incluses ou non dans la sortie.

Maintenant, utilisons notre fonction isEven pour filtrer les nombres impairs d'un tableau d'entrée en utilisant la méthode de filtrage :

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

La bibliothèque Functional Java appelle cette fonctionnalité le pliage .

Une fonction doit être spécifiée pour indiquer ce que signifie plier l'élément. Un exemple de ceci est la fonction Integers.add pour montrer que les entiers dans un tableau ou une liste doivent être ajoutés.

En fonction de ce que fait la fonction lors du pliage, le résultat peut être différent selon que vous commencez le pliage par la droite ou la gauche. C'est pourquoi la bibliothèque Functional Java fournit les deux versions:

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

Le premier foldLeft ajoute simplement tous les entiers. Alors que le second appliquera d'abord un filtre, puis ajoutera les entiers restants.

10. Conclusion

Cet article n'est qu'une brève introduction à la bibliothèque Functional Java.

Comme toujours, le code source complet de l'article est disponible à l'adresse over sur GitHub.