Programmation fonctionnelle en Java : niveau avancé
\n\nLa programmation fonctionnelle (PF) en Java a cessé d'être une simple tendance à la mode — c'est une partie intégrante du langage moderne depuis la version 8. Pour les développeurs ayant maîtrisé les lambdas de base et le Stream API, vient l'étape où une compréhension de concepts plus profonds est nécessaire : monades, composition de fonctions, évaluations paresseuses et structures de données immuables. Cet article est consacré aux techniques avancées de programmation fonctionnelle en Java, qui aideront à écrire un code plus propre, plus prévisible et thread-safe.
\n\n1. Composition de fonctions et curryfication
\n\nEn Java, les interfaces fonctionnelles Function, Predicate et Consumer fournissent des méthodes par défaut pour la composition : andThen() et compose(). Cela permet de construire des chaînes de transformations sans créer de variables intermédiaires.
Exemple de composition :
\nimport java.util.function.Function;\n\npublic class FunctionComposition {\n public static void main(String[] args) {\n Function<String, String> trim = String::trim;\n Function<String, String> toUpper = String::toUpperCase;\n Function<String, String> addExclamation = s -> s + "!";\n\n // Composition : trim -> toUpper -> addExclamation\n Function<String, String> process = trim.andThen(toUpper).andThen(addExclamation);\n\n String result = process.apply(" hello world ");\n System.out.println(result); // HELLO WORLD!\n }\n}\n\nCurryfication — c'est la transformation d'une fonction à plusieurs arguments en une séquence de fonctions à un seul argument. En Java, cela peut être émulé avec des lambdas imbriquées :
\n\nimport java.util.function.Function;\n\npublic class CurryingExample {\n // Fonction normale\n static int add(int a, int b) { return a + b; }\n\n // Version curryfiée\n static Function<Integer, Function<Integer, Integer>> curriedAdd = a -> b -> a + b;\n\n public static void main(String[] args) {\n // Application partielle\n Function<Integer, Integer> add5 = curriedAdd.apply(5);\n System.out.println(add5.apply(3)); // 8\n System.out.println(add5.apply(10)); // 15\n }\n}\n\nLa curryfication est utile pour créer des fonctions spécialisées basées sur des modèles généraux, notamment lors du travail avec des configurations.
\n\n2. Travail avancé avec le Stream API
\n\nLe Stream API en Java n'est pas simplement un remplacement des boucles. Comprendre ses capacités avancées permet d'améliorer considérablement les performances et la lisibilité du code.
\n\n2.1. Flux parallèles et splitteurs
\nLes flux parallèles utilisent ForkJoinPool. Cependant, leur efficacité dépend de la taille des données et de la nature des opérations. Pour un contrôle personnalisé du parallélisme, on peut utiliser Spliterator.
import java.util.Spliterator;\nimport java.util.stream.Stream;\nimport java.util.stream.StreamSupport;\n\npublic class CustomSpliterator {\n public static void main(String[] args) {\n // Création d'un flux à partir d'un itérateur avec un splitteur personnalisé\n Spliterator<Integer> spliterator = new Spliterator<>() {\n int current = 0;\n @Override\n public boolean tryAdvance(java.util.function.Consumer<? super Integer> action) {\n if (current < 100) {\n action.accept(current++);\n return true;\n }\n return false;\n }\n @Override\n public Spliterator<Integer> trySplit() {\n // Division la plus simple\n int mid = current + 50;\n if (mid >= 100) return null;\n Spliterator<Integer> prefix = new RangeSpliterator(curre