Aller au contenu

Programmation Fonctionnelle en Java : Partie 4 - Programmation fonctionnelle avancée

Découvrez comment la librairie Vavr révolutionne la programmation fonctionnelle en Java. Collections immuables, gestion avancée des erreurs, composition de fonctions : explorez des concepts avancés qui comblent les lacunes du JDK.

Programmation fonctionnelle avancée avec vavr

📝 Introduction : au-delà des limites du JDK

Si vous êtes arrivé ici sans avoir lu les précédentes parties de notre série sur la programmation fonctionnelle en Java, je vous recommande de consulter dans un premier temps :

Dans cet article, nous allons explorer Vavr, une librairie Java sans dépendance transitive, qui introduit des concepts avancés de programmation fonctionnelle. Vavr comble les lacunes du JDK en offrant des outils modernes comme des collections immuables, des types fonctionnels avancés et des patterns éprouvés. Vous êtes prêt ? Plongeons dans l'univers de Vavr !

🚀 Vavr : une introduction à la programmation fonctionnelle avancée

Qu'est-ce que Vavr ?

Vavr (anciennement Javaslang) est une librairie qui enrichit Java avec des fonctionnalités inspirées de langages fonctionnels comme Scala ou Kotlin. Elle propose :

  • Collections immuables : des structures de données sécurisées et sans effets de bord.
  • Types fonctionnels avancés : gestion d'erreurs, validation, composition de fonctions, etc.
  • Interopérabilité avec Java : Vavr s'intègre parfaitement dans un projet Java existant.
Pourquoi utiliser Vavr ?

Le JDK offre des outils fonctionnels de base (comme Optional ou les Streams), mais il reste limité pour des cas d'usage avancés. Vavr comble ces lacunes en introduisant des concepts comme :

  • Gestion typée des erreurs avec Either.
  • Validation accumulative pour des scénarios complexes.
  • Memoization pour optimiser les performances.

1. Installation et configuration

Pour intégrer Vavr dans votre projet Maven, ajoutez simplement la dépendance suivante à votre fichier pom.xml :

<dependency>
    <groupId>io.vavr</groupId>
    <artifactId>vavr</artifactId>
    <version>0.10.6</version>
</dependency>

Configuration pom.xml

Une fois configurée, vous êtes prêt à explorer les fonctionnalités avancées de Vavr.

2. Collections immuables : sécurité et fluidité

L'une des forces de Vavr réside dans ses collections immuables. Contrairement aux collections classiques du JDK, celles de Vavr sont thread-safe et ne peuvent pas être modifiées après leur création.

Exemple : listes immuables
import io.vavr.collection.List;

List<String> list = List.of("Java", "Scala", "Kotlin");
List<String> newList = list.append("Clojure"); // Retourne une nouvelle liste

System.out.println("Original: " + list);     // [Java, Scala, Kotlin]
System.out.println("Nouvelle: " + newList);  // [Java, Scala, Kotlin, Clojure]

Liste immuable

Exemple : Maps immuables
import io.vavr.collection.Map;

Map<String, Integer> prices = HashMap.of(
    "Coffee", 3,
    "Tea", 2
);
Map<String, Integer> newPrices = prices.put("Water", 1);

Map immuable

Ces collections permettent également des opérations fluides comme le filtrage ou le mapping :

import io.vavr.collection.List;

// ✅ Opérations fluides
List<String> result = List.range(1, 6)  // [1, 2, 3, 4, 5]
    .filter(n -> n % 2 == 0)            // [2, 4]
    .map(n -> "Number: " + n);          // [Number: 2, Number: 4]

Like a Stream but is not

3. Types fonctionnels avancés

Vavr introduit des types fonctionnels puissants qui simplifient la gestion des erreurs et des exceptions.

Option : Une alternative enrichie à Optional
Option<String> some = Option.of("Hello");
Option<String> none = Option.none();

String result1 = some.getOrElse("Default");        // "Hello"
String result2 = none.getOrElse("Default");        // "Default"

// Chaînage
String result3 = Option.of("  hello  ")
    .map(String::trim)
    .map(String::toUpperCase)
    .filter(s -> s.length() > 3)
    .getOrElse("Too short");                       // "HELLO"

Option

Either : Gestion typée des erreurs

Either permet de représenter soit une valeur correcte (Right), soit une erreur (Left).

public Either<String, Integer> divide(int a, int b) {
    if (b == 0) {
        return Either.left("Division par zéro");
    }
    return Either.right(a / b);
}

public void eitherExample() {
    String result1 = divide(10, 2)
        .fold(error -> "Erreur: " + error, success -> "Résultat: " + success);
    // "Résultat: 5"
    
    String result2 = divide(10, 0)
        .fold(error -> "Erreur: " + error, success -> "Résultat: " + success);
    // "Erreur: Division par zéro"
}

Either

Try : Gestion des exceptions

Avec Try, vous pouvez encapsuler des opérations susceptibles de lever des exceptions.

Try<Integer> result = Try.of(() -> Integer.parseInt("123"));
    
String message = result
    .map(n -> "Nombre: " + n)
    .getOrElse("Parsing échoué");                  // "Nombre: 123"

// Avec exception
Try<Integer> failed = Try.of(() -> Integer.parseInt("abc"));
String errorMessage = failed
    .recover(throwable -> -1)
    .map(n -> "Résultat: " + n)
    .get();                                        // "Résultat: -1"

Try

Validation : Accumulation d'erreurs

La validation est un cas d'usage courant dans les applications. Avec Vavr, vous pouvez accumuler plusieurs erreurs au lieu de vous arrêter à la première.

// ✅ Validation - Accumulation d'erreurs
public Validation<List<String>, Person> validatePerson(String name, int age, String email) {
    return Validation.combine(
        validateName(name),
        validateAge(age),
        validateEmail(email)
    ).map(Person::new);
}

private Validation<String, String> validateName(String name) {
    return name != null && !name.trim().isEmpty() 
        ? Validation.valid(name)
        : Validation.invalid("Nom requis");
}

private Validation<String, Integer> validateAge(int age) {
    return age >= 0 && age <= 120 
        ? Validation.valid(age)
        : Validation.invalid("Âge invalide");
}

private Validation<String, String> validateEmail(String email) {
    return email != null && email.contains("@") 
        ? Validation.valid(email)
        : Validation.invalid("Email invalide");
}

Validation

4. Composition de Fonctions : Construisez des pipelines puissants

Vavr facilite la composition de fonctions, un concept clé en programmation fonctionnelle.

Exemple : Composition avec andThen
// Function0 - fonction sans paramètre
Function0<String> getCurrentTime = () -> LocalDateTime.now().toString();
Function1<String, String> formatTime = time -> "Heure actuelle: " + time;

// Composition avec andThen
Function0<String> composedFunction = getCurrentTime.andThen(formatTime);
String result = composedFunction.get();
System.out.println(result); // "Heure actuelle: 2025-06-26T10:30:45"

Function composition

Exemple pratique : Pipeline de traitement de texte
Function1<String, String> trim = String::trim;
Function1<String, String> toUpper = String::toUpperCase;
Function1<String, Integer> getLength = String::length;

// andThen - composition de gauche à droite
Function1<String, Integer> pipeline1 = trim
    .andThen(toUpper)
    .andThen(getLength);

int result1 = pipeline1.apply("  hello  "); // 5

// compose - composition de droite à gauche
Function1<String, Integer> pipeline2 = getLength
    .compose(toUpper)
    .compose(trim);

int result2 = pipeline2.apply("  hello  "); // 5 (même résultat)

Function1

Exemple de currification, permettant d'appliquer partiellement des paramètres
Function2<Integer, Integer, Integer> add = (a, b) -> a + b;
Function2<Integer, Integer, Integer> multiply = (a, b) -> a * b;

// Currying - transformer Function2 en Function1
Function1<Integer, Integer> add5 = add.curried().apply(5);
Function1<Integer, Integer> multiplyBy3 = multiply.curried().apply(3);

// Composition de fonctions curryfiées
Function1<Integer, Integer> addThenMultiply = add5.andThen(multiplyBy3);

int result = addThenMultiply.apply(10); // (10 + 5) * 3 = 45

Function2

Memoization : Optimisez vos calculs

La mémorisation permet de mettre en cache les résultats d'une fonction pour éviter des calculs répétitifs.

// Fonction coûteuse
Function1<Integer, Integer> expensiveCalculation = n -> {
    System.out.println("Calcul pour: " + n);
    try { Thread.sleep(1000); } catch (InterruptedException e) {}
    return n * n;
};

// Version mémorisée
Function1<Integer, Integer> memoized = expensiveCalculation.memoized();

System.out.println(memoized.apply(5)); // Calcul effectué
System.out.println(memoized.apply(5)); // Résultat en cache
System.out.println(memoized.apply(3)); // Nouveau calcul
System.out.println(memoized.apply(5)); // Résultat en cache

Memoization

Avantages de Vavr

Collections :

  • Immuable par défaut
  • API fluide et expressive

Gestion d'erreurs :

  • Option plus riche qu'Optional
  • Either pour erreurs typées
  • Try pour exceptions
  • Validation pour accumulation d'erreurs

Composition :

  • Fonctions composables
  • Memoization intégrée
  • Currying automatique

Migration :

  • Compatible avec Java existant
  • Introduction progressive possible
  • Interopérabilité parfaite
  • Portage partiel de fonctionnalités Scala et Kotlin

Vavr transforme Java en langage fonctionnel moderne tout en gardant la familiarité Java.

✍️ Conclusion

Avec Vavr, Java franchit un cap décisif vers la programmation fonctionnelle moderne. Cette librairie ne se contente pas de combler les lacunes du JDK : elle transforme véritablement votre façon d'écrire du code Java.

🔑 Les apports essentiels de Vavr

Sécurité et robustesse : Les collections immuables et la gestion typée des erreurs avec EitherTry et Validation éliminent une grande partie des bugs courants en Java traditionnel.

Expressivité : le code devient plus lisible et déclaratif grâce aux compositions de fonctions et aux opérations fluides sur les collections.

Performance : la mémorisation automatique et les structures de données optimisées améliorent les performances sans complexité supplémentaire.

Adoption progressive : l'interopérabilité parfaite avec l'écosystème Java existant permet une migration en douceur, sans révolution brutale de votre codebase.

Vers une nouvelle ère du développement Java

Vavr démontre que Java peut rivaliser avec les langages fonctionnels modernes comme Scala ou Kotlin, tout en conservant sa stabilité et sa maturité. En adoptant Vavr, vous investissez dans un code plus maintenable, plus sûr et plus expressif.

La programmation fonctionnelle n'est plus l'apanage des langages académiques : avec Vavr, elle devient accessible et pratique pour tous les développeurs Java. Il est temps de franchir le pas et d'explorer ces nouveaux horizons !


Cet article conclut notre série sur la programmation fonctionnelle en Java. Nous espérons que ce voyage vous aura donné les clés pour écrire du code Java plus moderne et plus robuste. N'hésitez pas à expérimenter avec Vavr dans vos projets !

Dernier