Programmation fonctionnelle en Java : niveau avancé
La programmation fonctionnelle (PF) est un paradigme dans lequel les calculs sont considérés comme des calculs de fonctions mathématiques, évitant les changements d'état et les données mutables. Java, étant un langage orienté objet, a reçu avec la sortie de Java 8 des outils puissants pour la PF : les expressions lambda, l'API Stream et les interfaces fonctionnelles. Au niveau avancé, ces possibilités permettent d'écrire un code plus concis, plus sûr et plus expressif, en particulier lors du travail avec des collections, des opérations asynchrones et la gestion des erreurs.
Dans cet article, nous examinerons les techniques avancées de programmation fonctionnelle en Java : la composition de fonctions, les monades (Optional et CompletableFuture), l'API Stream avec traitement parallèle et le currying.
1. Composition de fonctions et interfaces fonctionnelles
En Java, les interfaces fonctionnelles sont des interfaces avec une seule méthode abstraite (SAM). Les plus couramment utilisées sont : Function<T, R>, Predicate<T>, Consumer<T> et Supplier<T>. L'utilisation avancée inclut la composition de fonctions à l'aide des méthodes andThen() et compose().
Exemple : chaîne de transformations
import java.util.function.Function;
public class FunctionComposition { public static void main(String[] args) { // Fonction : chaîne -> sa longueur Function<String, Integer> lengthFunction = String::length; // Fonction : nombre -> son carré Function<Integer, Integer> squareFunction = x -> x * x;
// Composition : chaîne -> longueur -> carré de la longueur Function<String, Integer> composedFunction = lengthFunction.andThen(squareFunction);
String text = "Hello, Java!"; int result = composedFunction.apply(text); // 12 * 12 = 144 System.out.println("Résultat : " + result); // 144 }}La méthode andThen() applique d'abord la première fonction, puis la seconde. compose() fait l'inverse : elle applique d'abord la fonction passée en argument, puis la première. Cela permet de construire des chaînes de transformations flexibles et réutilisables.
Exemple pratique : filtrage et transformation d'une liste
import java.util.List;import java.util.function.Function;import java.util.function.Predicate;import java.util.stream.Collectors;
public class PredicateAndFunction { public static void main(String[] args) { List<String> names = List.of("Alice", "Bob", "Charlie", "David", "Eve");
Predicate<String> startsWithA = s -> s.startsWith("A"); Function<String, String> toUpperCase = String::toUpperCase;
List<String> result = names.stream() .filter(startsWithA) .map(toUpperCase) .collect(Collectors.toList());
System.out.println(result); // [ALICE] }}2. Monades en Java : Optional et CompletableFuture
Une monade est un patron qui encapsule une valeur et fournit des opérations pour la transformer sans déballage explicite. En Java, les monades sont implémentées via Optional (pour la gestion de null) et CompletableFuture (pour les opérations asynchrones).
Optional comme monade
Les méthodes map(), flatMap() et filter() permettent de travailler en chaîne avec l'absence possible de valeur.
import java.util.Optional;
public class OptionalMonad { public static void main(String[] args) { // Création d'un Optional avec une valeur non nulle Optional<String> optional = Optional.of("Hello");
// Chaîne de transformations String result = optional .filter(s -> s.length() > 3) // filtre .m