← Java SE 21 — préparation OCP

Module 4 / 8 · 90 min

Lambdas, streams et réductions

Construire des transformations sans état partagé caché.

Lessons, exercises and questions in this path are in French. The interface follows the site language.

Your progress will appear here when JavaScript is active.

Progress and best score are stored only in this browser. No account is needed; clearing browser data removes them. Module times are estimates including exercises.

Comprendre le mécanisme

Une lambda implémente une interface fonctionnelle et ne capture que des variables locales finales ou effectivement finales. Un stream décrit un pipeline : les opérations intermédiaires sont généralement paresseuses et une opération terminale déclenche le traitement nécessaire. Un stream consommé ne doit pas être réutilisé. map transforme chaque élément ; flatMap aplatit des flux imbriqués. Optional exprime une présence possible, sans autoriser un get aveugle.

Raisonner et vérifier

Une réduction parallèle exige notamment une combinaison associative et une identité adaptée. Modifier une liste extérieure dans forEach introduit un état partagé et peut rompre la sûreté du traitement. Préférez des collecteurs qui construisent le résultat. Ne faites pas reposer une obligation métier sur peek : certaines optimisations ou opérations terminales peuvent éviter le parcours imaginé.

Construire un pipeline sans le consommer

Des opérations intermédiaires comme filter et map décrivent un traitement qui peut être paresseux. L’exécution nécessaire se produit avec une opération terminale selon le contrat du stream. Une chaîne de méthodes ne prouve donc pas que tous les effets ont déjà eu lieu à la création. Pour comprendre un résultat, identifiez source, étapes intermédiaires et terminal, puis tracez les valeurs qui traversent réellement chaque étape.

Understand this common mistake

map exécute toujours immédiatement tous les traitements.

Le parcours dépend notamment d’une opération terminale.

Une consommation n’est pas une collection réutilisable

Un stream possède un cycle de vie et ne doit généralement pas être réutilisé après une opération terminale. Pour recommencer un parcours, créez un nouveau stream depuis la source ou fournissez un mécanisme qui le produit. Conserver une référence au stream ne le transforme pas en cache de résultats. Le test utile réalise deux parcours distincts depuis la source au lieu de répéter une consommation invalide.

Understand this common mistake

Le même stream peut toujours être consommé indéfiniment.

Le cycle de vie interdit cette réutilisation générale.

Capture locale et état effectivement final

Une lambda peut capturer une variable locale finale ou effectivement finale. Réaffecter ensuite cette variable rompt ce contrat ; la compiler n’est pas rendu possible parce que la lambda s’exécute plus tard. Cette règle ne gèle pas tous les objets référencés par la variable. Un objet mutable peut encore porter des effets partagés : l’absence de réaffectation de référence ne prouve pas une fonction pure.

Understand this common mistake

Toute locale capturée peut être réaffectée librement ensuite.

La capture obéit à cette contrainte du langage.

Réduire avec une loi adaptée

Une réduction parallèle peut regrouper les opérations dans des ordres différents. La combinaison doit donc être associative avec une identité adaptée au contrat choisi. Une soustraction arbitraire ne possède pas cette propriété : changer les regroupements peut changer le résultat. Écrivez la loi attendue sur un petit jeu de valeurs et comparez plusieurs regroupements avant de conclure que la parallélisation préserve votre calcul.

Understand this common mistake

Toute soustraction arbitraire est une réduction parallèle associative.

L’ordre de regroupement peut modifier le résultat.

Éviter les effets partagés non protégés

Écrire dans une ArrayList partagée depuis un stream parallèle n’acquiert pas automatiquement une protection de concurrence. Même lorsque le résultat semble correct lors d’un essai, des entrelacements différents peuvent perdre ou corrompre des opérations. Préférez une collecte et une combinaison adaptées au stream lorsque c’est le besoin. Les effets externes, comme envoyer des messages, demandent aussi une politique d’ordre, de reprise et de doublons.

Understand this common mistake

Un stream parallèle rend toute ArrayList partagée thread-safe.

Le parallélisme ne protège pas automatiquement l’état mutable.

Le terminal définit le résultat

Un terminal peut produire une somme, une liste, une valeur optionnelle ou un effet selon l’opération choisie. Il faut donc lire son contrat au lieu de supposer que toute chaîne renvoie une collection. Une somme après filtre ne porte que sur les éléments conservés. Tracez la cardinalité et le type après chaque transformation ; cette méthode permet de vérifier un résultat sans exécuter mentalement toutes les méthodes en bloc.

Understand this common mistake

Tous les terminaux retournent nécessairement une liste.

Le résultat dépend de l’opération choisie.

Représenter l’absence avec Optional

Un résultat Optional annonce qu’une valeur peut être absente. Il ne justifie pas un accès supposant systématiquement sa présence. Définissez comment le consommateur traite le cas vide : valeur de repli, autre traitement ou erreur explicitement choisie. Évitez un défaut qui transforme une absence significative en valeur métier trompeuse. Les tests doivent établir le comportement pour une source vide et pour un élément effectivement trouvé.

Understand this common mistake

Optional garantit toujours une valeur présente.

Le consommateur doit traiter le cas vide.

Ordre de rencontre et ordre des effets

L’ordre d’une source et les garanties de l’opération terminale influencent ce qui peut être observé. Le parallélisme ne promet pas que tout effet externe apparaisse dans un ordre intuitif. Si le contrat public exige une séquence, choisissez les opérations qui la préservent ou séparez le calcul de la publication. Une impression console nominale ne suffit pas à démontrer une garantie d’ordre sous toutes les exécutions.

Understand this common mistake

Un stream parallèle conserve toujours tout ordre d’effet externe.

Les garanties d’ordre doivent être choisies et comprises.

Mesurer avant de paralléliser

Le traitement parallèle introduit une distribution du travail et des coûts de coordination. Sur une petite collection ou une opération très courte, ces coûts peuvent dépasser le gain. La nature des dépendances et des ressources partagées compte aussi. Mesurez sur des données représentatives et conservez le même contrat de résultat. Le mot parallel ne crée ni cœurs supplémentaires ni réduction universelle de la latence.

Understand this common mistake

parallel réduit nécessairement la latence de tout petit calcul.

Surcoûts et caractéristiques du travail comptent.

Ne pas modifier la source pendant le parcours

Le contrat de non-interférence évite de transformer arbitrairement la source pendant que le pipeline la parcourt. Une mutation cachée dans un callback peut rendre le raisonnement dépendant d’un ordre ou d’une implémentation. Séparez préparation des données et traitement, ou utilisez un modèle explicitement conçu pour la concurrence nécessaire. Un test doit explorer les cas de source partagée au lieu de conclure depuis une seule exécution locale.

Understand this common mistake

Modifier arbitrairement la source pendant son parcours est toujours sûr.

Le contrat doit préserver la cohérence du traitement.

Atelier guidé : lire, expliquer et vérifier le code

import java.util.List;
class Main {
    public static void main(String[] args) {
        var source = List.of(1, 2, 3);
        int sum = source.stream().filter(n -> n > 1).mapToInt(n -> n * 2).sum();
        System.out.print(sum);
    }
}

Le filtre de l’exercice retire 1 et conserve 2 puis 3. mapToInt transforme ces valeurs en 4 et 6, et sum déclenche le parcours utile avant d’additionner les résultats. La sortie vaut donc 10. Ni la somme de la source seule ni le double de toute la source ne respecte l’ordre des transformations montrées.

Rejouez le programme en retirant temporairement le terminal pour observer que la simple construction du pipeline n’est pas la même opération. Ajoutez ensuite une source vide et vérifiez le résultat défini par le terminal. Si vous envisagez une version parallèle, examinez séparément l’associativité de la réduction et les effets éventuels des callbacks ; la simplicité de cet exemple ne constitue pas une preuve pour tous les traitements.

Worked example

var total = java.util.List.of(2, 5, 8).stream()
    .filter(n -> n > 3)
    .mapToInt(Integer::intValue)
    .sum();
System.out.println(total);

Le filtre conserve 5 et 8 ; la réduction retourne 13. La liste source n’est pas modifiée.

Practical exercise

Regroupez des réservations par produit et calculez le total de quantités sans map globale mutable.

View a suggested solution

Utilisez groupingBy avec summingInt sur un record validé. Comparez le résultat séquentiel et parallèle sur les mêmes données et testez le flux vide.

Verification checklist

  • Identifier la terminaison.
  • Éviter effets de bord dans les lambdas.
  • Vérifier associativité et identité.

Knowledge check

Related lesson: Lambdas, streams et réductions ↗
Quand le pipeline filter/map réalise-t-il normalement son travail ?

Reference documentation ↗

Module validation

101 questions, local practice target of 75%. Suggested duration of 150 minutes, with no enforced deadline. Questions and choices are shuffled; corrections appear after submission. The quick lesson check and the final course exam remain separate exercises.

Start or resume the 101 questions →