Généricité, expressions lambda et flux en Java

Écris des classes et des méthodes Java qui fonctionnent avec tout type, tandis que le compilateur vérifie chaque utilisation, puis transmets un comportement à des méthodes sous forme de valeur grâce aux expressions lambda et aux références de méthodes. Trie des objets selon la règle de ton choix avec des comparateurs et traite des collections avec l’API Stream : filtre, transforme, trie, compte, additionne, regroupe et collecte des données en quelques étapes lisibles. Chaque exemple est un court extrait de code complet accompagné de son résultat exact ; les erreurs de compilation et les exceptions d’exécution courantes sont expliquées au fil du cours.

DébutantUnité 11 sur 11 de la formation Formation : Java orienté objet : classes, collections, héritage et tests

Ce que tu vas apprendre

  1. Utilise des paramètres de type pour écrire des classes, des interfaces et des méthodes qui fonctionnent avec de nombreux types, tout en laissant le compilateur vérifier chaque utilisation.
    • Définis et utilise des classes génériques
      Déclare des classes et des interfaces avec des paramètres de type, crée des objets avec des arguments de type et explique la sécurité des types ainsi obtenue par rapport au stockage de valeurs de type Object.
    • Écris des méthodes génériques
      Déclare des méthodes avec leurs propres paramètres de type, appelle-les avec des arguments de type déduits ou explicites et détermine quand rendre une méthode générique plutôt qu’une classe.
    • Restreins les paramètres de type avec des bornes et des types jokers
      Utilise des paramètres de type bornés pour appeler des méthodes sur une valeur de type générique, explique pourquoi une List<Integer> n’est pas une List<Number> et choisis entre ? extends et ? super.
    • Tiens compte des limites de l’effacement des types
      Explique comment le compilateur efface les arguments de type, quelles opérations la généricité interdit par conséquent et pourquoi il faut éviter les types bruts.
  2. Écris des expressions lambda et des références de méthodes pour des interfaces fonctionnelles, utilise les interfaces fonctionnelles standard de Java et transmets des comportements à des méthodes sous forme de valeurs.
    • Écris des expressions lambda pour des interfaces fonctionnelles
      Reconnais les interfaces fonctionnelles, écris des expressions lambda sous forme courte ou sous forme de bloc et explique comment le type cible donne son sens à une expression lambda.
    • Utilise les interfaces fonctionnelles standard
      Choisis entre Predicate, Function, Consumer, Supplier et les interfaces apparentées, combine-les et transmets-les à des méthodes de collection telles que removeIf et replaceAll.
    • Capture des variables et écris des références de méthodes
      Utilise dans une expression lambda des variables du code environnant, applique la règle des variables effectivement finales et remplace des expressions lambda simples par les quatre types de références de méthodes.
  3. Trie des listes selon la règle de ton choix à l’aide de comparateurs, d’une méthode compare écrite à la main à des chaînes construites avec comparing, thenComparing et reversed.
    • Écris un comparateur et utilise-le pour trier
      Implémente correctement le contrat de compare, trie des listes et des tableaux avec un comparateur et choisis entre Comparable et Comparator.
    • Combine des comparateurs avec comparing, thenComparing et reversed
      Construis des comparateurs à partir de clés de tri, ajoute des critères de départage, inverse des ordres, gère les valeurs nulles et trie les entrées d’une Map.
  4. Construis des pipelines de l’API Stream qui filtrent, transforment, trient et réduisent des données, et explique comment un pipeline s’exécute.
    • Construis un pipeline de flux et comprends son exécution
      Crée des flux à partir de collections, de tableaux et de plages de valeurs, distingue les opérations intermédiaires des opérations terminales et explique l’évaluation paresseuse, l’usage unique et pourquoi la source reste inchangée.
    • Filtre, transforme et réordonne des éléments
      Utilise filter, map, mapToInt, flatMap, distinct, sorted, limit et skip, et passe des flux d’objets aux flux de types primitifs et inversement.
    • Réduis un flux à un résultat unique
      Calcule des effectifs, des sommes, des moyennes, des extrema et des réponses vraies ou fausses avec des opérations terminales, et gère l’absence de résultat avec Optional.
  5. Rassemble les résultats des flux dans des listes, des ensembles, des Map et des chaînes, regroupe et partitionne des données avec des collecteurs et détermine quand un flux ou une boucle est le meilleur outil.
    • Collecte les résultats dans des listes, des ensembles, des Map et des chaînes
      Utilise toList, Collectors.toList, toSet, toCollection, joining et toMap, et gère les résultats non modifiables et les clés en double.
    • Regroupe et partitionne des données avec des collecteurs
      Répartis les données en groupes avec groupingBy et partitioningBy et résume chaque groupe avec un collecteur en aval.
    • Choisis entre flux et boucles pour réaliser une tâche complète
      Résous une tâche de traitement de données du début à la fin, compare une solution avec flux à une boucle et garde les pipelines sans effets de bord.

Généricité et flux en Java : cours

5 chapitres

1

Écris des classes et des méthodes génériques

Le code générique préserve une relation utile entre les entrées, les valeurs stockées et les résultats. Une classe peut conserver cette relation pour un objet, tandis qu’une méthode peut la choisir à chaque appel. Les bornes et les types jokers permettent aux API d’accepter les types nécessaires sans autoriser d’écritures non sûres. Le compilateur vérifie ces engagements avant d’effacer les paramètres de type, ce qui explique à la fois les avantages de la généricité et les opérations qu’elle ne peut pas prendre en charge à l’exécution.

1.1 Définis et utilise des classes génériques

Un conteneur devrait indiquer ce qu’il contient. Avec un paramètre de type, une même classe peut contenir des chaînes, des nombres ou tes propres objets, et le compilateur vérifie chaque utilisation avant l’exécution du programme.

Donne un paramètre de type à une classe

Dans class Box<T>, T est un paramètre de type : un nom pour un type que le code appelant fournira. Utilise-le pour les champs, les paramètres de méthodes et les résultats.

class Box<T> {
    private T value;

    public Box(T value) { this.value = value; }
    public T get() { return value; }
    public void set(T value) { this.value = value; }
}

Le nom du constructeur est Box, sans <T>. La déclaration après le nom de la classe introduit T pour le corps de la classe. Crée maintenant une boîte :

Box<String> b = new Box<>("tea");
String drink = b.get();
b.set("water");
System.out.println(drink);   // tea
System.out.println(b.get()); // water

String est l’argument de type. Pour cette variable, get() renvoie une String et set accepte une String. Aucun transtypage n’est nécessaire à la lecture. La tentative d’appel b.set(42) échoue à la compilation : « incompatible types: int cannot be converted to String ». L’erreur signale l’écriture incorrecte avant qu’une lecture ultérieure puisse échouer.

Pourquoi ne pas stocker un Object ?

Un champ Object peut référencer aussi bien une chaîne qu’un entier. Le code qui a besoin d’une chaîne doit effectuer un transtypage de l’objet renvoyé. Si un entier a été stocké, ce transtypage lève une ClassCastException à l’exécution du programme. Une Box<String> inscrit la relation voulue dans le type déclaré.

Le diamant <> dans new Box<>("tea") demande au compilateur de déduire les arguments de type. Il conserve les vérifications. Omettre complètement les chevrons, comme dans new Box("tea"), utilise un type brut et perd les vérifications liées au paramètre de type. Affecter une construction de type brut à une variable typée produit des avertissements de type non vérifié ; la variable typée continue à imposer son propre argument déclaré lors des appels suivants.

Utilise des classes enveloppes et plusieurs paramètres

Un argument de type doit être un type référence. Utilise Box<Integer>, pas Box<int>. Java convertit le littéral 5 en un Integer lors de la construction de new Box<Integer>(5) et le convertit à nouveau en int lorsqu’un calcul l’exige. Si n contient cette boîte, int next = n.get() + 1; donne 6.

Deux types indépendants nécessitent deux paramètres. Une Pair<K, V> peut avoir un champ K first et un champ V second. Pour une Pair<String, Integer>, ses accesseurs renvoient une String et un Integer. Les noms courants sont T pour un type, E pour un élément, K et V pour une clé et une valeur, et R pour un résultat.

Ajoute des paramètres de type aux interfaces
interface Container<T> {
    void put(T item);
    T take();
}

Une classe peut préserver le choix avec class Shelf<T> implements Container<T>, ou le fixer avec class NameList implements Container<String>. List<E> et Comparable<T> utilisent la même idée. Une classe générique reste une seule classe : Box<String> et Box<Integer> décrivent différentes utilisations vérifiées à la compilation, et non deux déclarations de classe. Ces types ne peuvent pas être affectés l’un à l’autre, car ils annoncent des types d’éléments différents.

Points clés

  • Un paramètre de type nomme un type dans une déclaration ; un argument de type le fournit lors d’une utilisation.
  • Un accesseur générique renvoie le type d’élément déclaré sans transtypage.
  • Le diamant préserve les vérifications de type ; un type brut perd les vérifications génériques.
  • Utilise des classes enveloppes telles que Integer comme arguments de type.
  • Les classes et les interfaces peuvent avoir plusieurs paramètres de type.

1.2 Écris des méthodes génériques

Certaines opérations nécessitent de choisir un type pour un seul appel, et non pour toute la durée de vie d’un objet. Une méthode générique introduit son propre paramètre de type et préserve une relation entre son entrée et son résultat.

Déclare le type avant le type de retour
import java.util.List;

class Lists {
    public static <T> T last(List<T> list) {
        return list.get(list.size() - 1);
    }

    public static void main(String[] args) {
        int number = last(List.of(4, 8, 15));
        String word = Lists.<String>last(List.of("x", "y"));
        System.out.println(number); // 15
        System.out.println(word);   // y
    }
}

Lis la déclaration par parties. static en fait une méthode de classe. <T> introduit un paramètre de type appartenant à la méthode. Le T suivant est le type de retour. List<T> indique que les éléments de la liste d’entrée et le résultat ont le même type.

Pour le premier appel, Java déduit que T est Integer. Le résultat peut ensuite être converti automatiquement en int. Dans le second appel, <String> est un témoin de type explicite, placé entre le point et le nom de la méthode. Ici, Lists.last(...) permettrait de déduire le même type. Un témoin nécessite un élément avant le point : un nom de classe pour une méthode statique, ou un objet tel que this pour une méthode d’instance.

Cette méthode attend une liste non vide. Sur une liste vide, size() - 1 vaut -1, donc get lève une IndexOutOfBoundsException. La généricité vérifie les types ; elle ne prouve pas qu’un indice existe.

Relie plusieurs types

Une méthode générique peut introduire plusieurs paramètres. Avec une classe Pair<K, V> dotée d’un constructeur et d’accesseurs, cette méthode complète inverse les rôles :

static <K, V> Pair<V, K> swap(Pair<K, V> p) {
    return new Pair<>(p.getSecond(), p.getFirst());
}

Pour la paire d’entrée ("Ada", 36) de type Pair<String, Integer>, le résultat est (36, "Ada") de type Pair<Integer, String>. La déclaration du type de retour exprime la relation ; un type de retour Object la masquerait et imposerait un transtypage au code appelant.

Une méthode telle que <T> int countEqual(List<T> list, T target) relie aussi ses entrées. Sa boucle peut comparer chaque élément à target avec equals. Avec ["a", "b", "a", "c", "a"] et "a", le nombre obtenu est 3. L’inférence peut choisir un supertype commun aux arguments ; répéter T n’impose donc pas toujours que deux expressions fournies en argument aient des types déclarés identiques.

Détermine à qui appartient le paramètre de type

Une Box<String> conserve son argument de type pour l’utilisation de cet objet. Lists.last choisit un nouvel argument de type à chaque appel. Une classe utilitaire ordinaire peut donc contenir des méthodes statiques génériques sans être elle-même générique.

Une méthode statique dans Box<T> ne peut pas utiliser le T de la classe : la méthode statique appartient à la classe partagée, et non à une boîte particulière. Déclare un paramètre distinct, par exemple static <E> Box<E> of(E value). Si une déclaration de méthode commence par static T first(...) sans <T> propre à la méthode, le compilateur indique qu’il ne trouve pas T dans une classe ordinaire. Ajoute la déclaration avant le type de retour ; remplacer le type de retour par Object ferait perdre la relation utile.

Points clés

  • Une méthode générique déclare ses propres paramètres de type avant son type de retour.
  • L’inférence est effectuée séparément pour chaque appel.
  • Un témoin de type apparaît après un point et avant le nom de la méthode.
  • Utilise plusieurs paramètres de type lorsque les entrées et les sorties ont plusieurs rôles liés.
  • Une méthode statique doit déclarer son propre paramètre de type.
  • La sécurité des types ne vérifie pas qu’une entrée respecte l’exigence de non-vacuité d’une méthode.

1.3 Restreins les paramètres de type avec des bornes et des types jokers

Un T sans restriction ne prend en charge que les opérations disponibles sur Object. Les bornes ajoutent une garantie utile. Les types jokers permettent à un paramètre d’accepter une famille de types génériques sans prétendre que tous leurs types d’éléments sont identiques.

Exige les opérations dont tu as besoin
static <T extends Comparable<T>> T largest(List<T> list) {
    T best = list.get(0);
    for (T x : list) {
        if (x.compareTo(best) > 0) {
            best = x;
        }
    }
    return best;
}

La borne indique qu’un T peut se comparer à un autre T. En partant de 3, l’appel largest(List.of(3, 11, 7)) remplace la meilleure valeur par 11, puis la laisse inchangée pour 7. Il renvoie 11. Pour ["pear", "apple", "plum"], le dernier mot dans l’ordre naturel est "plum". Une liste vide provoque un échec à get(0).

extends introduit aussi les bornes pour les interfaces. <T extends Number> rend doubleValue() disponible. Les bornes multiples utilisent &, avec une classe en premier s’il y en a une : <T extends Number & Comparable<T>>.

Pourquoi List n’est pas List

Integer étend Number, mais List<Integer> n’est pas un sous-type de List<Number>. Les types génériques sont invariants. Si cette affectation était autorisée, le code utilisant la référence List<Number> pourrait ajouter un Double ; le code qui détient la liste d’origine le lirait alors comme un Integer. Un paramètre List<Number> accepte les implémentations telles que ArrayList<Number>, mais refuse List<Integer>.

Lis avec extends ; ajoute avec super
static double sum(List<? extends Number> nums) {
    double total = 0;
    for (Number n : nums) total += n.doubleValue();
    return total;
}

static void addOneToThree(List<? super Integer> target) {
    target.add(1);
    target.add(2);
    target.add(3);
}

? extends Number désigne un type inconnu qui est Number ou l’un de ses sous-types. sum accepte des listes d’entiers, de nombres à virgule flottante ou de nombres. La méthode lit chaque élément comme un Number. sum(List.of(1.5, 2.5)) donne 4,0. Elle ne peut pas ajouter 3 : le type d’élément inconnu pourrait être Double. Les règles de type autorisent l’ajout de null, même si une liste particulière peut le refuser à l’exécution.

? super Integer désigne Integer ou l’un de ses supertypes. La seconde méthode peut ajouter des entiers à une List<Integer>, une List<Number> ou une List<Object> modifiable. Une lecture par ce paramètre ne fournit que le type Object : les éléments existants pourraient être d’autres nombres ou objets.

ParamètreOpération sûre sur les éléments
List<? extends T>Lire un élément comme un T
List<? super T>Ajouter un T ; lire comme un Object
List<T>Lire et écrire avec le même type nommé
List<?>Lire comme un Object ; consulter la taille

La règle mnémotechnique est producteur : extends, consommateur : super. Elle décrit le sens du transfert des éléments, et non l’immuabilité de la collection entière. Par exemple, il peut rester possible de supprimer des éléments par une vue utilisant extends.

Utilise un paramètre nommé lorsqu’un type doit relier plusieurs positions, notamment une entrée et un type de retour. Utilise un type joker lorsque seul un ensemble de types d’entrée acceptés compte. Collections.copy lit depuis une List<? extends T> et écrit dans une List<? super T> ; sa destination doit déjà avoir assez de positions à remplacer.

Points clés

  • Les bornes rendent les méthodes du type qui sert de borne disponibles sur T.
  • L’invariance des types génériques protège une liste contre l’écriture d’éléments de type incorrect.
  • Utilise ? extends T pour lire des éléments comme des T.
  • Utilise ? super T pour ajouter des valeurs de type T ; les valeurs lues sont de type Object.
  • Un type joker restreint les opérations effectuées par une référence ; il ne rend pas l’objet immuable.
  • Utilise un paramètre de type nommé pour relier les types des entrées et du résultat.
  • Collections.copy remplace les éléments aux positions existantes de la destination.

1.4 Tiens compte des limites de l’effacement des types

La généricité donne au compilateur plus d’informations qu’un test ordinaire de classe à l’exécution. Comprendre ce qui reste après la compilation explique les avertissements liés aux types bruts et plusieurs opérations que Java refuse.

Ce que l’effacement supprime

Java vérifie les arguments de type, puis efface les paramètres de type en les remplaçant par leurs bornes. Un T non borné devient Object ; un T extends Number devient Number. Le compilateur insère des transtypages là où ils sont nécessaires à la lecture des valeurs. Il n’existe qu’une seule classe Box à l’exécution, donc ceci affiche true :

System.out.println(
    new Box<String>("a").getClass()
    == new Box<Integer>(1).getClass()
);

La déclaration de Box peut être celle de la classe générique dotée d’un champ de valeur, d’un constructeur et d’un accesseur. Les deux constructions ont la même classe à l’exécution, même si leurs utilisations déclarées font l’objet de vérifications différentes à la compilation.

Opérations qui nécessitent un type connu à l’exécution

Dans une classe générique, new T() ne compile pas. Java ne sait pas quel constructeur possède le type fourni par le code appelant. Accepte une fabrique pour créer la valeur :

import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;

class Factory {
    static <T> T create(Supplier<T> factory) {
        return factory.get();
    }

    public static void main(String[] args) {
        List<String> words = create(ArrayList::new);
        words.add("ready");
        System.out.println(words); // [ready]
    }
}

Le fournisseur définit le comportement de création. new T[10] et new List<Integer>[2] sont également refusés comme créations de tableaux génériques. Stocke une séquence dans une ArrayList<T> lorsque tu n’as pas besoin du comportement d’un tableau.

Pour un Object obj, obj instanceof List<String> ne peut pas tester de façon sûre l’argument de type des éléments. Utilise obj instanceof List<?> pour vérifier s’il s’agit d’une liste, puis traite ses éléments comme étant de type inconnu. Ce test n’établit pas que les éléments sont des chaînes.

Membres statiques partagés et surcharges après effacement

Un champ tel que static T shared ne peut pas utiliser le T de la classe englobante. Toutes les paramétrisations partagent le membre statique. Une méthode statique peut introduire son propre <E>, indépendant du paramètre de la classe.

Deux surcharges print(Set<String> s) et print(Set<Integer> s) ne peuvent pas coexister dans une même classe. Toutes deux deviennent après effacement une méthode acceptant Set, ce qui provoque un conflit de noms. Donne des noms différents aux opérations ou conçois une seule méthode qui gère leur comportement commun.

Remonte d’un avertissement de type non vérifié jusqu’à la lecture incorrecte
List raw = new ArrayList();
raw.add("text");
raw.add(42);
for (Object value : raw) {
    String text = (String) value;
    System.out.println(text);
}

Ce code affiche text, puis le transtypage de 42 lève une ClassCastException. La déclaration brute peut déclencher un diagnostic de type brut ; les ajouts déclenchent des avertissements d’appel non vérifié, car les vérifications génériques ont été contournées. La remplacer par List<String> déplace l’échec à add(42), lors de la compilation. Un avertissement de type non vérifié signale une garantie perdue, donc un risque d’échec à l’exécution. Corrige l’utilisation du type brut ou le transtypage non sûr au lieu de supposer qu’une lecture ultérieure sera sûre.

Points clés

  • L’effacement remplace les paramètres de type par leurs bornes, ou par Object lorsqu’ils ne sont pas bornés.
  • Les utilisations paramétrées partagent une seule classe à l’exécution.
  • Utilise une fabrique fournie en argument à la place de new T().
  • Les types des paramètres après effacement ne peuvent pas distinguer des surcharges génériques.
2

Transmets des comportements avec des expressions lambda

Une méthode peut accepter un comportement aussi bien que des données. Les interfaces fonctionnelles décrivent la forme de ce comportement et les expressions lambda fournissent le travail à effectuer. Les interfaces standard donnent des noms clairs aux tâches courantes : tester, transformer, agir et fournir. Les variables capturées permettent à une règle d’utiliser des informations environnantes, tandis que les références de méthodes désignent des comportements existants. Le respect du type cible relie chaque élément de syntaxe à un appel de méthode ordinaire.

2.1 Écris des expressions lambda pour des interfaces fonctionnelles

Une méthode peut accepter une règle comme argument. Une expression lambda écrit cette règle là où elle est utilisée, tandis qu’une interface fonctionnelle indique à Java quelles entrées la règle prend et ce qu’elle renvoie.

Réponds à une seule méthode abstraite

Une interface fonctionnelle possède une méthode abstraite à implémenter. Les déclarations correspondant à des méthodes d’instance publiques de Object, comme equals(Object), ne comptent pas. Les méthodes par défaut et les méthodes statiques n’ajoutent pas de méthode abstraite à implémenter. Comparator<T> et Runnable sont des interfaces fonctionnelles.

@FunctionalInterface
interface Operation {
    int apply(int a, int b);
}

class Combine {
    static int combine(int a, int b, Operation op) {
        return op.apply(a, b);
    }

    public static void main(String[] args) {
        Operation add = (a, b) -> a + b;
        System.out.println(add.apply(3, 4)); // 7
        System.out.println(combine(3, 4, (x, y) -> x * y)); // 12
    }
}

L’interface fournit le nom de méthode apply, les deux types de paramètres int et le type de résultat int. L’expression lambda fournit les noms des paramètres et le corps. Elle crée une implémentation utilisable par l’intermédiaire de Operation ; elle n’appelle pas apply tant que le programme ne l’invoque pas.

combine transmet toujours ses deux nombres à op. Le code appelant change le comportement en fournissant une addition ou une multiplication. C’est aussi de cette façon que sort reçoit une règle d’ordre et que filter reçoit une condition.

Lis la syntaxe

Une expression lambda comporte des paramètres, ->, puis une expression ou un bloc. Un seul paramètre dont le type est déduit peut s’écrire sans parenthèses : w -> w.length() > 5. En l’absence de paramètre, il faut écrire () ; deux paramètres ou plus nécessitent des parenthèses, comme dans (a, b) -> a * b. Les types explicites sont autorisés : (int a, int b) -> a + b.

Un corps sous forme d’expression renvoie automatiquement son résultat. Un bloc qui produit une valeur doit la renvoyer :

Operation max = (a, b) -> {
    if (a > b) {
        return a;
    }
    return b;
};
System.out.println(max.apply(3, 9)); // 9

Chaque chemin dans ce bloc renvoie un int. Ajouter des accolades autour de a + b sans return ne crée pas un bloc valide qui renvoie une valeur.

Laisse le type cible donner son sens à l’expression

Le type cible est l’interface fonctionnelle attendue dans une affectation, un argument ou un retour. Le texte x -> x * 2 peut convenir à Function<Integer, Integer> ou à UnaryOperator<Integer>. Les types de son paramètre et de son résultat proviennent de ce type cible. Object o = () -> 1; échoue parce que Object ne fournit aucune méthode fonctionnelle.

@FunctionalInterface demande au compilateur de vérifier la définition de l’interface. C’est une protection utile si quelqu’un ajoute plus tard une seconde méthode abstraite distincte. Sans l’annotation, une telle interface peut encore être déclarée, mais une expression lambda ne peut pas répondre à ses deux exigences distinctes.

Une expression lambda peut aussi entrer en conflit avec des noms locaux. Si la méthode environnante déclare déjà int a, elle ne peut pas déclarer un paramètre d’expression lambda nommé a dans cette portée. Choisis un autre nom de paramètre. Par rapport à une classe anonyme qui implémente Comparator<String>, une expression lambda supprime la répétition des déclarations de classe et de méthode ; le calcul qui détermine l’ordre doit toujours respecter le contrat du comparateur.

Points clés

  • Une expression lambda fournit l’implémentation de la méthode abstraite d’une interface fonctionnelle.
  • L’interface cible détermine les types d’entrée, le type de résultat et le nom de la méthode.
  • Un corps sous forme d’expression renvoie directement une valeur ; un bloc qui renvoie une valeur nécessite return.
  • @FunctionalInterface vérifie la définition de l’interface.
  • Un paramètre d’expression lambda ne peut pas redéclarer une variable locale de la portée environnante.

2.2 Utilise les interfaces fonctionnelles standard

Java nomme déjà les formes courantes de comportement. Choisis une interface selon le nombre d’entrées et le type de résultat, puis appelle sa méthode fonctionnelle pour exécuter le comportement.

Choisis la forme des entrées et de la sortie
InterfaceMéthode fonctionnelleRôle
Predicate<T>boolean test(T t)Tester une condition
Function<T, R>R apply(T t)Transformer une valeur
Consumer<T>void accept(T t)Effectuer une action
Supplier<T>T get()Fournir une valeur sans entrée

Ces interfaces se trouvent dans java.util.function. Pour des chaînes, w -> w.length() > 5 convient à un prédicat, String::length à une fonction qui renvoie un Integer, et System.out::println à un consommateur. Un fournisseur tel que () -> new ArrayList<String>() crée une nouvelle liste à chaque exécution de get(). Une condition utilisée par filter nécessite un Predicate<T>, même si une Function<T, Boolean> pourrait aussi calculer une valeur booléenne dans un autre code.

UnaryOperator<T> est une fonction qui transforme un T en un autre T de même type. BinaryOperator<T> combine deux valeurs de type T en un T. BiFunction<T, U, R>, BiPredicate<T, U> et BiConsumer<T, U> prennent deux entrées. Les formes pour types primitifs évitent la conversion en objets enveloppes : IntPredicate.test(int), IntBinaryOperator.applyAsInt(int, int) et ToIntFunction<T>.applyAsInt(T).

Combine des règles dans un ordre précis
import java.util.function.Function;
import java.util.function.Predicate;

class Rules {
    public static void main(String[] args) {
        Predicate<String> isLong = w -> w.length() > 5;
        Predicate<String> longB = isLong.and(w -> w.startsWith("b"));
        System.out.println(longB.test("banana")); // true
        System.out.println(isLong.negate().test("kiwi")); // true

        Function<Integer, Integer> twice = x -> x * 2;
        Function<Integer, Integer> inc = x -> x + 1;
        System.out.println(twice.andThen(inc).apply(5)); // 11
        System.out.println(twice.compose(inc).apply(5)); // 12
    }
}

and exige que les deux conditions soient vraies, or qu’au moins l’une le soit, et negate inverse la réponse. Predicate.not(p) nie aussi un prédicat. andThen exécute d’abord la fonction receveuse ; compose exécute d’abord son argument. Ici, la première chaîne calcule 10 puis 11, tandis que la seconde calcule 6 puis 12.

Transmets une règle à une méthode
static <T> List<T> keep(List<T> list, Predicate<T> test) {
    List<T> kept = new ArrayList<>();
    for (T x : list) {
        if (test.test(x)) kept.add(x);
    }
    return kept;
}

keep(List.of(4, 9, 12, 15), n -> n % 3 == 0) donne [9, 12, 15]. Grâce à T, la méthode est réutilisable avec différents types d’éléments ; grâce au prédicat, son comportement de sélection est réutilisable.

Pour une liste modifiable [3, 8, 1, 10, 6], removeIf(n -> n < 5) laisse [8, 10, 6]. replaceAll(n -> n * 2) donne ensuite [16, 20, 12]. Ces méthodes modifient la liste ; forEach accepte un consommateur pour des actions telles que l’affichage. Copie le résultat de List.of(...) dans une ArrayList avant d’utiliser des opérations qui le modifient.

Pour les Map, forEach((key, value) -> ...) prend un BiConsumer. merge(word, 1, Integer::sum) stocke 1 pour un nouveau mot et incrémente un effectif existant. computeIfAbsent(key, k -> new ArrayList<>()) crée une valeur pour une clé absente ou associée à null et renvoie la valeur obtenue. Ces méthodes réunissent la recherche et la règle de mise à jour.

Points clés

  • Choisis une interface fonctionnelle selon la forme de ses entrées et de son résultat.
  • andThen exécute d’abord la fonction receveuse ; compose exécute d’abord son argument.
  • removeIf teste un prédicat ; replaceAll applique un opérateur unaire.
  • merge combine une valeur existante de la Map avec une valeur fournie.
  • computeIfAbsent peut construire et stocker une valeur absente d’une Map.
  • Les spécialisations pour types primitifs fonctionnent directement avec des valeurs primitives.

2.3 Capture des variables et écris des références de méthodes

Une expression lambda peut utiliser des informations de la méthode qui l’entoure. Lorsque son corps ne fait qu’appeler une méthode existante, une référence de méthode exprime souvent le même comportement plus directement.

Capture une variable locale
int limit = 10;
Predicate<Integer> small = n -> n < limit;
System.out.println(small.test(7)); // true

L’expression lambda capture limit. Une variable locale capturée doit être final ou effectivement finale : elle reçoit une valeur une seule fois et n’est jamais réaffectée. Ajouter limit = 20; n’importe où après cette première affectation empêche la compilation de la capture, même si la réaffectation apparaît avant l’expression lambda.

La règle s’applique à la variable locale, pas à tout l’état de l’objet qu’elle référence. Ceci compile :

StringBuilder log = new StringBuilder();
Runnable action = () -> log.append("ran");
action.run();
System.out.println(log); // ran

log référence toujours le même constructeur de chaîne. La mise à jour d’un champ de l’objet environnant est aussi autorisée, mais des modifications cachées peuvent rendre le comportement difficile à suivre.

Essayer numbers.forEach(n -> total += n) avec une variable locale int total = 0 échoue, car cela modifie la variable capturée. Utilise une boucle lorsque la tâche consiste en une mise à jour, ou calcule directement un résultat avec numbers.stream().mapToInt(n -> n).sum().

Lis les quatre formes de références

Une référence de méthode utilise ::, sans liste d’arguments. L’interface fonctionnelle cible fournit les entrées et le résultat attendu.

TypeRéférenceExpression lambda équivalente
Méthode statiqueInteger::parseInts -> Integer.parseInt(s)
Méthode d’instance liéeSystem.out::printlnx -> System.out.println(x)
Méthode d’instance non liéeString::lengths -> s.length()
ConstructeurArrayList::new() -> new ArrayList<>()

Pour la référence liée, l’objet receveur est déjà fixé : le PrintStream référencé par System.out. Pour la référence non liée, le premier argument de l’interface fonctionnelle devient l’objet receveur. Ainsi, String::compareToIgnoreCase se développe en (a, b) -> a.compareToIgnoreCase(b) : la première chaîne est l’objet receveur et la seconde est l’argument de la méthode.

Adapte la référence à un type cible
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;

class References {
    public static void main(String[] args) {
        Function<String, Integer> parse = Integer::parseInt;
        Function<String, Integer> length = String::length;
        Supplier<List<String>> fresh = ArrayList::new;
        System.out.println(parse.apply("42")); // 42
        System.out.println(length.apply("stream")); // 6
        System.out.println(fresh.get()); // []
    }
}

Une référence de constructeur peut aussi prendre des arguments. Student::new convient à une Function<String, Student> lorsqu’un constructeur correspondant prend une String. Le type cible détermine quelle surcharge de constructeur est sélectionnée.

Student::getName désigne un accesseur d’instance appelé sur chaque étudiant fourni ; il ne nécessite pas d’accesseur statique. Écris une expression lambda lorsqu’un travail supplémentaire est nécessaire, comme dans s -> s.getPoints() * 2 ou w -> w.length() > 5. Écrire String::length() est incorrect : la référence désigne un comportement sans l’invoquer à cet endroit.

Points clés

  • Une variable locale capturée doit être finale ou effectivement finale.
  • Une référence finale peut tout de même référencer un objet mutable.
  • Une référence liée fixe son objet receveur ; une référence non liée le prend dans le premier argument.
  • Les références de constructeurs utilisent ClassName::new.
  • Les références de méthodes n’ont pas de parenthèses d’appel.
3

Ordonne des objets avec Comparator

Les mêmes objets peuvent nécessiter plusieurs ordres utiles : les noms par ordre alphabétique, les points par ordre décroissant ou les groupes avec un critère de départage selon les scores. Les comparateurs séparent ces règles de l’ordre naturel de la classe. Une comparaison correcte doit respecter la cohérence des signes et des relations d’ordre. Les méthodes de construction rendent les clés visibles, mais la position d’une inversion et la gestion des égalités ou des valeurs nulles déterminent toujours le résultat.

3.1 Écris un comparateur et utilise-le pour trier

L’ordre naturel d’un objet n’est pas toujours celui dont une tâche a besoin. Un Comparator<T> distinct te permet de trier les mêmes objets selon différentes règles sans modifier leur classe.

Lis le signe de compare

compare(a, b) renvoie un nombre négatif lorsque a vient en premier, zéro lorsque les deux ont le même rang et un nombre positif lorsque a vient après b. Seul le signe compte. Un comparateur ne renvoie pas de booléen.

Comparable<T> appartient à la classe et fournit son ordre naturel par compareTo. Un comparateur est une valeur distincte transmise à une méthode de tri. Les chaînes ont un ordre naturel des caractères, mais un comparateur peut les trier par longueur.

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

class SortWords {
    public static void main(String[] args) {
        List<String> words = new ArrayList<>(List.of("ccc", "a", "bb"));
        Comparator<String> byLength =
            (a, b) -> Integer.compare(a.length(), b.length());
        words.sort(byLength);
        System.out.println(words); // [a, bb, ccc]
    }
}

Le comparateur extrait les longueurs et compare les nombres. words.sort modifie la liste modifiable et renvoie void ; il ne renvoie pas de nouvelle liste. Trier le résultat de List.of(...) lève une UnsupportedOperationException ; l’exemple en fait donc d’abord une copie sous forme d’ArrayList.

Compare les nombres sans erreur

Utilise Integer.compare ou Double.compare. Une soustraction peut inverser le signe à cause d’un dépassement de capacité : 2_000_000_000 - (-2_000_000_000) vaut -294 967 296 en tant que int. Le plus grand nombre est alors placé à tort en premier dans l’ordre croissant. Integer.compare(2_000_000_000, -2_000_000_000) renvoie correctement 1.

Convertir par transtypage une différence de valeurs double en int fait aussi perdre de l’information. (int) (1.2 - 0.7) vaut 0, bien que la première valeur soit plus grande. Double.compare(1.2, 0.7) renvoie un résultat positif.

Garde un ordre cohérent

Inverser les entrées doit inverser le signe de la comparaison. Si a précède b et b précède c, a doit précéder c. Les valeurs dont la comparaison donne zéro doivent avoir le même rang par rapport à toute autre valeur. Enfreindre ces règles peut donner un ordre incorrect ou amener le tri à lever une IllegalArgumentException avec « Comparison method violates its general contract! ». Renvoyer uniquement 0 ou 1 ne permet pas d’exprimer les deux sens.

Les tris de listes et les tris de tableaux d’objets avec comparateur en Java sont stables : les éléments à égalité conservent leur ordre initial. Pour les étudiants Ada 85, Ben 72, Chen 85, Dara 90 et Eli 64, le tri par points croissants donne Eli, Ben, Ada, Chen, Dara. Ada reste avant Chen parce qu’ils sont à égalité et qu’Ada apparaissait en premier.

Les exemples d’étudiants utilisent cette classe et une liste modifiable :

class Student {
    private final String name;
    private final String group;
    private final int points;

    Student(String name, String group, int points) {
        this.name = name;
        this.group = group;
        this.points = points;
    }
    String getName() { return name; }
    String getGroup() { return group; }
    int getPoints() { return points; }
}
List<Student> students = new ArrayList<>(List.of(
    new Student("Ada", "A", 85), new Student("Ben", "B", 72),
    new Student("Chen", "A", 85), new Student("Dara", "B", 90),
    new Student("Eli", "A", 64)));
students.sort(Comparator.comparingInt(Student::getPoints));
students.forEach(s -> System.out.println(s.getName()));
// Eli, Ben, Ada, Chen, Dara, each on a separate line
Choisis une méthode de tri

list.sort(cmp) et Collections.sort(list, cmp) modifient une liste modifiable. list.sort(null) demande l’ordre naturel. Arrays.sort(array, cmp) accepte un tableau d’objets tel que Integer[] ; il n’existe pas de surcharge avec comparateur pour un int[].

L’ordre naturel des chaînes compare les unités de code UTF-16. Les majuscules A–Z précèdent les minuscules a–z, donc ["banana", "Cherry", "apple"] devient [Cherry, apple, banana]. String.CASE_INSENSITIVE_ORDER donne [apple, banana, Cherry] pour ces mots. Choisis explicitement la règle de comparaison du texte lorsque les majuscules et les minuscules ne doivent pas déterminer l’ordre.

Points clés

  • Un comparateur renvoie un résultat de comparaison signé, pas un booléen.
  • Comparable fournit l’ordre naturel ; un comparateur fournit un ordre distinct.
  • Utilise les méthodes de comparaison numérique pour éviter les dépassements de capacité et la troncature.
  • Les tris stables conservent l’ordre initial des éléments à égalité.
  • Le tri d’une liste modifie une liste modifiable et renvoie void.
  • Les tableaux de types primitifs ne peuvent pas recevoir de comparateur.

3.2 Combine des comparateurs avec comparing, thenComparing et reversed

Un ordre fondé sur plusieurs clés se lit plus facilement comme une suite de décisions : comparer la clé principale, puis comparer une seconde clé uniquement si la première donne une égalité. Les méthodes de construction de comparateurs expriment directement ces décisions.

Extrais une clé et départage les égalités

Pour un Student doté de getName(), getGroup() et getPoints(), Comparator.comparing(Student::getName) ordonne par nom. comparingInt(Student::getPoints) extrait un int sans le convertir en objet enveloppe.

Comparator<Student> ranking =
    Comparator.comparingInt(Student::getPoints)
        .reversed()
        .thenComparing(Student::getName);

Ce comparateur classe d’abord les points les plus élevés, puis les noms par ordre croissant en cas d’égalité de points. Pour Ada (A, 85), Ben (B, 72), Chen (A, 85), Dara (B, 90), Eli (A, 64), il donne Dara, Ada, Chen, Ben, Eli. La comparaison des noms ne s’exécute qu’en cas d’égalité de points.

Place reversed au bon niveau

reversed() inverse tout ce qui a été construit avant lui. Inverser le classement après thenComparing inverserait aussi les noms au sein de chaque groupe à égalité de score. Pour classer les groupes par ordre croissant et les points par ordre décroissant dans chaque groupe, inverse uniquement le comparateur de points :

Comparator<Student> byGroup =
    Comparator.comparing(Student::getGroup)
        .thenComparing(
            Comparator.comparingInt(Student::getPoints).reversed());

Le résultat est Ada, Chen, Eli, Dara, Ben : le groupe A précède le groupe B ; dans A, 85 précède 64. Placer .reversed() à la fin inverserait à la fois l’ordre des groupes et celui des points par rapport à la chaîne précédente.

Choisis les règles pour le texte et les valeurs nulles

Comparator.naturalOrder() suit Comparable ; reverseOrder() inverse cet ordre. Fournis un comparateur de clés lorsqu’une clé nécessite un traitement particulier, comme dans Comparator.comparing(Student::getName, String.CASE_INSENSITIVE_ORDER).

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

class SortNullable {
    public static void main(String[] args) {
        List<String> words = new ArrayList<>();
        words.add("b");
        words.add(null);
        words.add("a");
        words.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
        System.out.println(words); // [null, a, b]
    }
}

Encadre la comparaison au niveau où apparaît la valeur nulle. nullsLast autour du comparateur d’étudiants entier gère les objets étudiants nuls. Un extracteur de nom qui renvoie null nécessite un comparateur prenant en charge les valeurs nulles pour la clé du nom elle-même. Les comparateurs d’ordre naturel et les comparateurs de clés ordinaires ne gèrent pas automatiquement les clés nulles.

Trie les entrées d’une Map et résous les difficultés d’inférence

Une HashMap ne garantit aucun ordre de rencontre. Crée un flux à partir de ses entrées et choisis explicitement un ordre :

stock.entrySet().stream()
    .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
    .forEach(System.out::println);

Pour rice=12, oats=3 et flour=7, ce code affiche rice=12, flour=7, oats=3, sur des lignes distinctes. Il ordonne les entrées du flux sans réorganiser stock. Ajoute thenComparing(Map.Entry.comparingByKey()) si les valeurs à égalité nécessitent un ordre prévisible des clés.

Le <String, Integer> explicite aide l’inférence dans l’appel chaîné. De même, Comparator.comparing(s -> s.getName()).reversed() peut conduire à déduire que s est un Object. Utilise Student::getName ou (Student s) -> s.getName() pour fournir le type manquant. Un comparateur nommé peut être réutilisé pour le tri de listes, les opérations sur les flux sorted, min et max, ou une Map ou un ensemble ordonné.

Points clés

  • comparing extrait une clé ; les méthodes pour types primitifs évitent de convertir les clés numériques en objets enveloppes.
  • thenComparing n’est utilisé que lorsque la comparaison précédente donne une égalité.
  • reversed() inverse le comparateur déjà construit.
  • Gère les valeurs nulles là où elles apparaissent : objets entiers ou clés extraites.
  • Trier un flux d’entrées ne réordonne pas sa Map source.
4

Traite des collections avec des flux

Un pipeline de flux transforme une collection en une suite de décisions de traitement. Suis les valeurs et leurs types : un filtre les sélectionne, une transformation les modifie et une opération terminale produit la réponse. L’exécution paresseuse signifie que certains éléments peuvent ne jamais être examinés, tandis que le tri nécessite toute l’entrée. Les flux numériques et les valeurs optionnelles rendent les types de résultats explicites, y compris lorsqu’aucune valeur ne subsiste ou qu’un total numérique est trop grand.

4.1 Construis un pipeline de flux et comprends son exécution

Un flux décrit comment traiter une séquence de valeurs. Il lit une source, fait passer les éléments par des opérations intermédiaires et se termine par une opération terminale qui produit un résultat ou effectue une action.

Repère les trois parties
import java.util.List;

class CountPasses {
    public static void main(String[] args) {
        List<Integer> points = List.of(72, 45, 90, 58, 81);
        long passed = points.stream()
            .filter(p -> p >= 60)
            .count();
        System.out.println(passed); // 3
        System.out.println(points); // [72, 45, 90, 58, 81]
    }
}

points.stream() est la source. filter est une opération intermédiaire : elle renvoie un autre flux. count est l’opération terminale : elle renvoie un long. Les valeurs 72, 90 et 81 passent le filtre. La lecture par le flux n’a pas supprimé 45 ni 58 de points.

Choisis une source

Les collections proposent stream(). Une Map fournit des flux par entrySet(), keySet() ou values(). Arrays.stream(array) crée un flux des éléments d’un tableau ; un int[] produit un IntStream. Stream.of("a", "b", "c") fournit les valeurs énumérées.

IntStream.range(1, 5) contient 1, 2, 3, 4, la fin étant exclue. rangeClosed(1, 5) inclut 5 et sa somme vaut 15. Stream.iterate(1, n -> n * 2) n’a pas de fin fixe ; .limit(5).toList() collecte [1, 2, 4, 8, 16]. Il est essentiel de limiter le résultat lorsqu’on consomme cette séquence infinie.

Suis l’exécution paresseuse

Les opérations intermédiaires sont paresseuses. Le simple fait d’écrire names.stream().map(String::toUpperCase); n’effectue pas les transformations. Une opération terminale lance le traitement, et les éléments ne sont consommés qu’au besoin.

String first = java.util.stream.Stream.of("one", "two", "three", "four")
    .filter(w -> w.length() > 3)
    .map(String::toUpperCase)
    .findFirst()
    .orElse("none");
System.out.println(first); // THREE

Les deux premiers mots ne passent pas le filtre. "three" passe, devient "THREE" et satisfait findFirst. Le dernier mot n’est pas nécessaire. C’est un court-circuit : la réponse est connue avant que toute la source soit lue.

sorted a besoin de toute l’entrée avant de pouvoir émettre la première valeur triée. Certaines autres opérations avec état, comme distinct en mode séquentiel, peuvent émettre des valeurs au fur et à mesure que les entrées arrivent. La place d’une opération dans la chaîne influe sur la quantité de travail nécessaire.

Crée un nouveau flux pour un autre résultat

Après une opération terminale, le flux est consommé. Réutiliser ce flux lève généralement une IllegalStateException avec « stream has already been operated upon or closed ». Appelle de nouveau points.stream() pour un autre pipeline. Une collection est un stockage réutilisable ; un flux est un chemin de traitement unique.

Créer un flux ne copie pas ses éléments. Garde la source inchangée pendant son traitement. map produit des valeurs transformées ; il ne les réécrit pas dans la liste. Pour modifier volontairement une liste, des méthodes telles que replaceAll et removeIf expriment directement cette tâche. Utilise une opération par ligne afin que l’ordre du traitement et le type de résultat restent visibles.

Points clés

  • Un pipeline comporte une source, des opérations intermédiaires et une opération terminale.
  • Les opérations intermédiaires sont paresseuses.
  • Le court-circuit peut éviter de lire le reste d’une source.
  • sorted doit recevoir toute l’entrée avant d’émettre une sortie triée.
  • Un flux consommé doit être remplacé par un nouveau flux issu de la source.
  • Créer un flux ne copie ni ne réécrit sa source.

4.2 Filtre, transforme et réordonne des éléments

Suis à la fois les valeurs et leur type après chaque opération : une expression lambda ultérieure reçoit l’élément actuel du flux, et non l’objet source d’origine.

Sélectionne avant de transformer

filter conserve les éléments qui satisfont un prédicat. map remplace chaque élément par une valeur calculée. Pour des étudiants dotés d’accesseurs pour le nom et les points, la sélection selon les points doit précéder la transformation en noms :

List<String> names = students.stream()
    .filter(s -> s.getPoints() >= 80)
    .map(Student::getName)
    .sorted()
    .toList();

Pour Ada 85, Ben 72, Chen 85, Dara 90 et Eli 64, ce code produit [Ada, Chen, Dara]. Avant map, le type des éléments est Student. Après cette opération, le type est String : un accesseur de points n’est donc plus disponible.

Passe à un flux de type primitif

Stream<Integer> contient des références vers des objets enveloppes et n’a pas de méthode sum(). mapToInt(Integer::intValue) produit un IntStream, qui fournit des opérations numériques. De même, mapToInt(String::length) mesure les longueurs des chaînes sous forme de valeurs primitives.

import java.util.List;
import java.util.stream.IntStream;

class Windows {
    public static void main(String[] args) {
        List<Integer> window = IntStream.rangeClosed(1, 10)
            .skip(2)
            .limit(3)
            .boxed()
            .toList();
        System.out.println(window); // [3, 4, 5]
    }
}

skip(2) retire 1 et 2 du flux. limit(3) conserve les trois valeurs suivantes. Inverser ces appels ne laisse que 3. boxed() revient à un Stream<Integer> afin que les éléments puissent être collectés en tant qu’objets. mapToObj crée d’autres objets à partir de valeurs primitives ; les flux de long et de double possèdent des opérations de conversion correspondantes.

Supprime les doublons et choisis un ordre

distinct utilise l’égalité des objets. ["map", "filter", "map", "sorted", "filter"] devient [map, filter, sorted], puis sorted() donne [filter, map, sorted]. Pour tes propres classes, implémente des méthodes equals et hashCode cohérentes si des valeurs de champs identiques doivent être considérées comme des doublons.

sorted() utilise l’ordre naturel. Fournis un comparateur pour un autre ordre ou pour des objets sans ordre naturel. Le tri d’un flux ordonné est stable. Trier les cinq étudiants par points décroissants, puis en prendre deux, sélectionne Dara et Ada : Ada précède Chen dans la liste d’origine alors qu’ils ont le même score. Appeler limit(2) avant le tri n’examinerait que les deux premiers étudiants de la liste d’origine.

Aplatis des séquences imbriquées
List<Integer> all = List.of(
        List.of(1, 2), List.of(3), List.of(4, 5, 6))
    .stream()
    .flatMap(List::stream)
    .toList();
System.out.println(all); // [1, 2, 3, 4, 5, 6]

Chaque liste interne devient un flux, et flatMap réunit ses éléments en un seul flux. Remplacer ici flatMap par map donne un Stream<Stream<Integer>> : un objet flux par liste interne, sans aplatissement.

peek transmet une valeur sans la modifier et peut l’afficher pour le débogage. Son action ne s’exécute que pour les éléments effectivement parcourus, et une implémentation peut ignorer une étape inutile, par exemple lors de l’optimisation de count. Ne l’utilise pas pour effectuer un travail indispensable. Pour obtenir une seule valeur maximale, utilise max(comparator) au lieu de trier tous les éléments et d’en prendre un.

Points clés

  • filter conserve le type des éléments ; map peut le changer.
  • Les flux de types primitifs fournissent des opérations numériques absentes de Stream<Integer>.
  • L’ordre de skip, limit et du tri change les éléments sélectionnés.
  • distinct suit l’égalité ; le tri d’un flux ordonné préserve l’ordre des éléments à égalité.
  • flatMap aplatit les flux produits à partir des éléments d’entrée.
  • peek sert à l’inspection, pas aux effets indispensables du programme.

4.3 Réduis un flux à un résultat unique

Choisis l’opération terminale selon le résultat dont tu as besoin : un effectif, un nombre, une valeur de vérité, un élément ou une combinaison générale. Une entrée vide compte lorsqu’aucune valeur ne peut être choisie comme résultat.

Calcule un nombre

count() renvoie un long. Sur un flux de type primitif, sum() renvoie un nombre du type primitif du flux. Pour les points [72, 45, 90, 58, 81], filtrer les valeurs d’au moins 60 puis les additionner donne 243. La moyenne de ces trois valeurs est 81,0.

import java.util.List;

class PointResults {
    public static void main(String[] args) {
        List<Integer> points = List.of(72, 45, 90, 58, 81);
        int sum = points.stream().filter(p -> p >= 60)
            .mapToInt(Integer::intValue).sum();
        double average = points.stream().filter(p -> p >= 60)
            .mapToInt(Integer::intValue).average().orElse(0.0);
        System.out.println(sum);     // 243
        System.out.println(average); // 81.0
    }
}

Le second pipeline utilise un nouveau flux. Une somme vide vaut 0, mais une moyenne calculée sur une entrée vide n’a pas de valeur. average() renvoie un OptionalDouble, et orElse(0.0) fournit une valeur de repli choisie. Cette valeur par défaut est une décision du programme, pas la moyenne mathématique d’un ensemble vide.

Une somme de type int peut provoquer un dépassement de capacité. Additionner deux valeurs de 2 000 000 000 produit -294 967 296 en tant que int. Convertis avant d’additionner : mapToInt(...).asLongStream().sum() donne 4 000 000 000 en tant que long.

Gère une valeur ou son absence

Un Optional<T> contient une valeur ou aucune. Sur un flux d’objets, min, max, findFirst, findAny et une réduction sans élément neutre renvoient des valeurs optionnelles. points.stream().max(Integer::compare) donne Optional[90] ; l’affichage du conteneur inclut le texte qui entoure la valeur.

Utilise orElse pour une valeur de repli, orElseGet pour un fournisseur de repli, ifPresent pour une action ou map pour transformer une valeur présente. Filtrer les étudiants ayant plus de 95 points, transformer le premier résultat en nom et appliquer orElse("nobody") donne "nobody" pour les données des cinq étudiants. Appeler get() ou orElseThrow() sur une valeur optionnelle vide lève une NoSuchElementException.

Pose une question dont la réponse est vraie ou fausse

anyMatch demande si au moins une valeur satisfait le prédicat, allMatch si toutes les valeurs le satisfont et noneMatch si aucune ne le satisfait. Ces opérations peuvent s’arrêter dès que la réponse est connue. Sur une entrée vide, anyMatch est faux, tandis que allMatch et noneMatch sont vrais : aucune valeur ne contredit l’une ou l’autre de ces affirmations universelles.

Combine avec un élément neutre

Stream.of(3, 5, 7).reduce(0, Integer::sum) accumule 0, 3, 8, 15. Pour la multiplication, commence par 1 et utilise (a, b) -> a * b pour obtenir 105. Un élément neutre laisse une valeur inchangée pour l’opération ; 10 n’est pas un élément neutre pour l’addition et décalerait à tort le total à 25. L’accumulateur doit être associatif afin que le regroupement ne change pas le résultat.

Sans élément neutre, reduce(Integer::max) donne Optional[7] et une valeur optionnelle vide pour une entrée vide. forEach, lui, effectue une action et renvoie void ; il convient à l’affichage. Utilise une réduction ou une collecte lorsque la tâche consiste à produire une valeur de résultat.

Points clés

  • Les effectifs utilisent long ; les résultats des flux numériques suivent leur type primitif.
  • Une somme vide vaut zéro ; une moyenne ou un extremum sur une entrée vide n’a pas de valeur.
  • Utilise les opérations prévues pour le cas vide d’une valeur optionnelle avant de lire sa valeur.
  • Sur un flux vide, allMatch et noneMatch sont vrais ; anyMatch est faux.
  • Une réduction nécessite un élément neutre et un accumulateur associatif.
  • Convertis en long avant d’additionner des valeurs dont la somme dépasse la plage de int.
5

Collecte et exploite les résultats des flux

Un résultat de flux utile peut être une liste, un ensemble, une Map, une chaîne ou une synthèse pour chaque groupe. Le collecteur détermine ce qui est stocké et ce qui se passe lorsque des clés se répètent. Le regroupement combine des règles de sélection et des synthèses en aval ; le partitionnement fournit deux groupes booléens. Une tâche complète peut ensuite interroger la Map obtenue. Comparer ce pipeline à une boucle t’aide à choisir un code lisible et à séparer la construction du résultat des modifications de l’état extérieur.

5.1 Collecte les résultats dans des listes, des ensembles, des Map et des chaînes

Un collecteur transforme un flux en une structure de résultat utile. Détermine ce que le résultat doit prendre en charge : doublons, ordre, modifications ultérieures ou clés de Map en double. Les garanties diffèrent selon les collecteurs.

Choisis une liste ou un ensemble

stream.toList() renvoie une liste non modifiable dans l’ordre de rencontre. Ajouter ou supprimer des éléments lève une UnsupportedOperationException. collect(Collectors.toList()) collecte aussi une liste, mais ne garantit ni une implémentation particulière ni la possibilité de la modifier. Pour une liste que tu modifieras, demande-la explicitement :

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

class Gather {
    public static void main(String[] args) {
        List<String> words = Stream.of("a", "b", "a")
            .collect(Collectors.toCollection(ArrayList::new));
        words.add("c");
        System.out.println(words); // [a, b, a, c]
        System.out.println(Stream.of("a", "b", "a")
            .collect(Collectors.toSet()).size()); // 2
    }
}

toSet supprime les doublons et ne garantit aucun ordre. toCollection(TreeSet::new) demande un ensemble trié. Une liste conserve les éléments en double ; choisis un ensemble lorsque le résultat représente des valeurs distinctes.

Assemble du texte

Collectors.joining(", ") assemble des chaînes avec un séparateur, sans séparateur final. Sa forme à trois arguments ajoute aussi un préfixe et un suffixe :

String names = Stream.of("Ada", "Chen", "Dara")
    .collect(Collectors.joining(", ", "[", "]"));
System.out.println(names); // [Ada, Chen, Dara]

Les éléments doivent être des valeurs CharSequence, généralement des chaînes. Transforme les étudiants avec Student::getName ou les nombres avec String::valueOf avant de les assembler. La jointure renvoie une seule chaîne, et non une liste dont l’affichage serait simplement similaire.

Choisis les clés et les valeurs d’une Map

Collectors.toMap(keyFunction, valueFunction) calcule une entrée à partir de chaque élément. Avec les cinq étudiants Ada (A, 85), Ben (B, 72), Chen (A, 85), Dara (B, 90), Eli (A, 64), associer les noms aux points donne cinq entrées telles que Ada=85 et Eli=64.

Les clés de regroupement sont différentes : plusieurs étudiants appartiennent au même groupe. toMap(Student::getGroup, Student::getPoints) à deux arguments lève une IllegalStateException lorsqu’une clé se répète, même si les deux valeurs sont 85. Une Map nécessite une seule valeur résultante par clé, et cette forme ne fournit aucune règle pour la choisir.

Fournis un troisième argument pour fusionner les doublons :

Map<String, Integer> totals = students.stream()
    .collect(Collectors.toMap(
        Student::getGroup, Student::getPoints, Integer::sum));

Le groupe A reçoit 85 + 85 + 64 = 234 ; le groupe B reçoit 72 + 90 = 162. Remplacer Integer::sum par Math::max conserve le nombre de points le plus élevé par groupe, A=85 et B=90. Utilise la règle qui répond à la tâche, pas une première valeur choisie arbitrairement.

toMap ne garantit aucun ordre particulier pour la Map. Un quatrième argument peut demander un ordre, par exemple TreeMap::new pour des clés triées. toMap(Student::getName, Student::getPoints, (a, b) -> a, TreeMap::new) conserve la première valeur en cas de noms en double et trie par nom. Un collecteur évite aussi de créer une liste extérieure et de la modifier dans forEach ; le résultat est construit par l’opération conçue pour le rassembler.

Points clés

  • Stream.toList() est non modifiable.
  • Collectors.toList() ne garantit pas une implémentation modifiable.
  • Utilise toCollection lorsqu’un type de collection précis est nécessaire.
  • joining rassemble du texte avec un séparateur, un préfixe et un suffixe facultatifs.
  • toMap à deux arguments refuse les clés en double, même lorsque les valeurs associées sont égales.
  • Une fonction de fusion résout les cas de clés répétées dans une Map.
  • Demande une fabrique de Map lorsque son ordre compte.

5.2 Regroupe et partitionne des données avec des collecteurs

Un résultat regroupé répond à une question pour chaque clé. Une partition répond à une question par oui ou par non avec deux groupes. Un collecteur en aval détermine ce que chaque groupe contient.

Regroupe selon une clé calculée

groupingBy(classifier) produit une Map qui associe chaque clé rencontrée à une liste de ses éléments. Regrouper ["fig", "kiwi", "pear", "plum", "apple", "mango", "banana"] selon la longueur des mots associe à la longueur 4 la liste [kiwi, pear, plum]. Les longueurs absentes n’ont pas d’entrée ; rechercher l’une d’elles renvoie null. Utilise donc getOrDefault(key, List.of()) lorsqu’une liste vide est utile.

import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

class WordGroups {
    public static void main(String[] args) {
        List<String> words = List.of(
            "fig", "kiwi", "pear", "plum", "apple", "mango", "banana");
        Map<Integer, Long> counts = words.stream()
            .collect(Collectors.groupingBy(
                String::length, TreeMap::new, Collectors.counting()));
        System.out.println(counts); // {3=1, 4=3, 5=2, 6=1}
    }
}

La fonction de classification choisit des clés entières. TreeMap::new ordonne ces clés. counting() remplace la liste de chaque groupe par sa taille sous forme de Long. Le type du résultat est donc Map<Integer, Long>, et non une Map d’entiers vers des entiers.

Change ce que contient un groupe

Avec les étudiants Ada (A, 85), Ben (B, 72), Chen (A, 85), Dara (B, 90), Eli (A, 64), le regroupement selon Student::getGroup offre ces possibilités :

Collecteur en avalValeur sous AType de valeur
counting()3Long
summingInt(Student::getPoints)234Integer
averagingInt(Student::getPoints)78.0Double
mapping(Student::getName, Collectors.toList())[Ada, Chen, Eli]List<String>

Un collecteur en aval travaille à l’intérieur de chaque groupe. Pour A, la moyenne est 234 divisé par 3, soit 78,0 ; celle de B est 81,0. maxBy et minBy stockent un Optional contenant l’extremum de chaque groupe. groupingBy seul ne garantit ni type de Map ni ordre des clés particulier ; sa forme avec fabrique de Map permet de choisir explicitement.

Répartis selon deux réponses
Map<Boolean, List<String>> partition = students.stream()
    .collect(Collectors.partitioningBy(
        s -> s.getPoints() >= 80,
        Collectors.mapping(Student::getName, Collectors.toList())));

La liste associée à vrai est [Ada, Chen, Dara], et celle associée à faux est [Ben, Eli]. Les deux clés booléennes sont présentes, même si l’un des groupes ne contient aucun étudiant. Avec counting() en aval, les groupes vides contiennent 0 sous forme de Long. À l’inverse, groupingBy avec une fonction de classification booléenne ne crée que les clés rencontrées.

Traite la Map construite

Un résultat regroupé est une Map ordinaire. Parcours-la avec forEach((group, value) -> ...), ou crée un flux à partir de ses entrées. Pour les moyennes A=78.0 et B=81.0, averages.entrySet().stream().max(Map.Entry.comparingByValue()) renvoie un Optional<Map.Entry<String, Double>> contenant B=81.0. L’Optional contient l’entrée gagnante ; ce n’est pas la simple valeur 81,0. Une étape distincte peut transformer cette entrée en sa clé ou sa valeur et gérer le cas d’une Map vide.

Points clés

  • Une fonction de classification choisit les clés des groupes ; un regroupement simple stocke des listes d’éléments.
  • Un collecteur en aval change la forme du résultat de chaque groupe.
  • counting produit un Long ; les collecteurs de moyenne produisent un Double.
  • Une partition contient toujours les deux clés booléennes.
  • La recherche d’une clé de regroupement absente renvoie null, pas un groupe vide.

5.3 Choisis entre flux et boucles pour réaliser une tâche complète

Une tâche complète de traitement de données comporte souvent deux étapes : résumer les entrées, puis interroger le résultat. Choisis les flux lorsque ces étapes se lisent naturellement comme des opérations standard, et les boucles lorsqu’un contrôle explicite rend le travail plus clair.

Calcule la moyenne des relevés, puis sélectionne les villes

Supposons qu’un Reading possède getCity() et getCelsius(). Voici les relevés, en degrés Celsius : Oslo 4,5, Lima 19,0, Oslo 6,0, Cairo 27,5 et Lima 21,0.

class Reading {
    private final String city;
    private final double celsius;
    Reading(String city, double celsius) {
        this.city = city;
        this.celsius = celsius;
    }
    String getCity() { return city; }
    double getCelsius() { return celsius; }
}

Crée la liste d’entrée avant chacune des deux implémentations :

List<Reading> readings = List.of(
    new Reading("Oslo", 4.5), new Reading("Lima", 19.0),
    new Reading("Oslo", 6.0), new Reading("Cairo", 27.5),
    new Reading("Lima", 21.0));
Map<String, Double> averages = readings.stream()
    .collect(Collectors.groupingBy(
        Reading::getCity,
        TreeMap::new,
        Collectors.averagingDouble(Reading::getCelsius)));

List<String> warmCities = averages.entrySet().stream()
    .filter(e -> e.getValue() > 15)
    .map(Map.Entry::getKey)
    .toList();

Le premier résultat est {Cairo=27.5, Lima=20.0, Oslo=5.25}. La moyenne d’Oslo est (4.5 + 6.0) / 2, soit 5,25. Celle de Lima est 20,0. Le second résultat est [Cairo, Lima] ; la TreeMap fournit les villes par ordre croissant. Filtre les moyennes après le regroupement : filtrer d’abord les relevés individuels répond à une autre question.

Pour le relevé individuel le plus élevé, utilise readings.stream().max(Comparator.comparingDouble(Reading::getCelsius)). Cette opération renvoie une valeur optionnelle contenant le relevé de Cairo, pas nécessairement la ville dont la moyenne est la plus élevée. Ces tâches peuvent donner des réponses différentes avec d’autres données.

Exprime le même calcul avec des boucles
Map<String, List<Double>> byCity = new TreeMap<>();
for (Reading r : readings) {
    byCity.computeIfAbsent(r.getCity(), k -> new ArrayList<>())
        .add(r.getCelsius());
}
Map<String, Double> averages = new TreeMap<>();
for (Map.Entry<String, List<Double>> e : byCity.entrySet()) {
    double sum = 0;
    for (double value : e.getValue()) sum += value;
    averages.put(e.getKey(), sum / e.getValue().size());
}
System.out.println(averages);
// {Cairo=27.5, Lima=20.0, Oslo=5.25}

Ce sont deux implémentations possibles du même résultat ; chaque groupe est non vide, car il a été créé par un relevé. Le flux rend explicites le regroupement et la moyenne dans un seul collecteur. Les boucles montrent les listes intermédiaires et la somme.

Garde un traitement prévisible

Une expression lambda de pipeline devrait effectuer son calcul à partir de son entrée sans modifier l’état extérieur. Pour les points [72, 45, 90, 58, 81], filter(p -> p >= 60).toList() produit directement [72, 90, 81]. Ajouter des éléments à une liste extérieure dans forEach cache cette construction du résultat dans une action.

Ne modifie pas une source ArrayList dans son pipeline : ajouter des éléments depuis un forEach peut lever une ConcurrentModificationException. Collecte d’abord les nouveaux éléments séparément, puis ajoute-les une fois le traitement terminé. Les règles de type de Java peuvent autoriser la mutation d’un objet par une référence finale capturée ; cela ne rend pas pour autant cette mutation adaptée à un flux.

Choisis le déroulement le plus clair

Utilise une boucle lorsque tu as besoin d’indices adjacents, d’une condition d’arrêt compliquée ou de plusieurs mises à jour coordonnées. Utilise les flux pour exprimer lisiblement une sélection, une transformation et une collecte. La syntaxe des flux ne garantit aucune vitesse à elle seule. Donne un nom à un prédicat ou à un comparateur long, et découpe une longue chaîne à un résultat intermédiaire qui a un sens. Pour déboguer, inspecte avec peek ou collecte une étape dans une liste et affiche-la ; poursuis avec un nouveau flux issu de cette liste au lieu de réutiliser un pipeline consommé.

Points clés

  • Regroupe avant de filtrer lorsque la condition concerne les moyennes des groupes.
  • Le relevé individuel maximal et la moyenne de groupe maximale répondent à des questions différentes.
  • Un collecteur exprime directement la construction du résultat.
  • Évite les mises à jour extérieures et la mutation de la source dans le comportement du pipeline.
  • Utilise des boucles lorsque des indices ou un contrôle coordonné rendent la tâche plus claire.
  • Inspecte une étape collectée, puis utilise un nouveau flux pour la suite du travail.

Généricité et flux en Java : exemples

5 Leçons Interactives

1

Construis une API réutilisable qui garantit la sécurité des types

17 étapes
Suis une boîte typée, une méthode générique et un transfert entre listes pour voir ce que Java peut vérifier avant l’exécution.
  1. 1
    IntroductionPréserve la relation entre les types

    Un conteneur réutilisable doit garder la trace de ce que ses utilisateurs peuvent y placer et en retirer. Un paramètre de type porte cette relation dans une déclaration.

    Tu vas construire une boîte, déplacer le choix du type vers une méthode, puis vérifier ce qui change lorsqu’un paramètre utilise un type joker. À chaque étape, demande-toi ce que le compilateur sait d’une valeur avant l’exécution du programme.

  2. 2
    ExplicationDéclare la boîte

    T nomme le type choisi par le code appelant dans cette classe :

    class Box<T> {
        private T value;
        Box(T value) { this.value = value; }
        T get() { return value; }
        void set(T value) { this.value = value; }
    }
    

    Le champ, l’entrée du constructeur, le résultat de l’accesseur et l’entrée du mutateur utilisent tous le même T. La déclaration introduit un paramètre ; aucun type de valeur stockée particulier n’a encore été choisi.

  3. 3
    ExempleUtilise un type concret

    Crée une boîte et lis sa valeur :

    Box<String> b = new Box<>("tea");
    String drink = b.get();
    b.set("water");
    

    String fournit l’argument de type. L’accesseur renvoie une chaîne sans transtypage, et le mutateur accepte des chaînes. Le diamant à droite demande à Java de déduire les arguments. Il ne supprime pas les vérifications ; omettre complètement les chevrons créerait une utilisation de type brut.

  4. 4
    DéfiPrévois l’écriture rejetée

    La Box<T> générique possède void set(T value). Après Box<Integer> b = new Box<>(8);, est-ce que b.set("nine") compile ? Explique à quel moment l’échec se produit.

    ---reveal---

    L’appel échoue à l’écriture parce que cette référence attend un Integer, pas une String. Aucune lecture ultérieure n’est nécessaire pour découvrir l’erreur. Le littéral 8 peut être converti en Integer ; les arguments de type eux-mêmes doivent toujours être des types références, donc Box<int> est incorrect.

  5. 5
    LienChoisis un type pour chaque appel

    Une méthode utilitaire peut introduire son propre paramètre de type :

    static <T> T last(List<T> list) {
        return list.get(list.size() - 1);
    }
    

    Le premier <T> déclare le paramètre, et le T suivant nomme le type de résultat. L’appeler sur [4, 8, 15] renvoie l’Integer 15 ; l’appeler sur ["x", "y"] renvoie la chaîne "y". La méthode nécessite une liste non vide, indépendamment de ses vérifications de type.

  6. 6
    ConseilÉcris un témoin après le point

    Java déduit généralement l’argument de type de la méthode à partir de la liste fournie. Un appel peut aussi l’indiquer :

    String word = Lists.<String>last(words);
    

    Le témoin de type suit le point et précède last. Une méthode statique dans une classe générique a toujours besoin de son propre paramètre : static <E> Box<E> of(E value). Le T de la classe appartient aux utilisations paramétrées des instances, tandis que la méthode statique appartient à la classe partagée.

  7. 7
    DéfiRelie l’entrée et la sortie

    Une méthode utilitaire doit renvoyer un élément d’une List<E> sans imposer de transtypage écrit par le code appelant. Une personne écrit static E first(List<E> xs). Quelle déclaration manque ?

    ---reveal---

    Ajoute <E> avant le type de retour : static <E> E first(List<E> xs). Cela introduit un paramètre propre à la méthode et relie les éléments de son entrée à son résultat. Écrire Object comme résultat ferait perdre cette relation utile au lieu de la rétablir.

  8. 8
    ExplicationRends la comparaison disponible

    Un T sans restriction n’a pas d’opération compareTo connue. Une borne peut fournir cette garantie :

    <T extends Comparable<T>>
    

    Une méthode largest(List<T>) peut alors conserver un T best, comparer chaque x à best et remplacer ce dernier lorsque x.compareTo(best) > 0. Pour 3, 11 et 7, les meilleures valeurs sont 3, puis 11, puis 11. Le retour reste du type d’élément propre à la liste.

  9. 9
    Point cléProtège une liste d’entiers

    Un Integer est un Number, mais une List<Integer> n’est pas une List<Number>.

    Si cette affectation de liste était autorisée, le code utilisant la référence vers la liste de nombres pourrait ajouter un Double. Le code qui détient la liste d’origine le lirait alors comme un Integer. L’invariance des types génériques empêche l’affectation afin que les lectures et les écritures respectent leurs garanties. ArrayList<Number> est accepté comme List<Number>, car son argument de type pour les éléments correspond toujours.

  10. 10
    ExempleTransfère dans le sens autorisé

    Un type joker avec extends permet de lire selon un type supérieur connu ; un type joker avec super permet d’insérer un type inférieur connu.

    static <T> void append(
            List<? super T> dst,
            List<? extends T> src) {
        dst.addAll(src);
    }
    

    Avec une destination List<Number> modifiable et une source List<Integer>, Java peut choisir Integer pour T. Chaque élément de la source est acceptable pour la destination, sans prétendre que les arguments de type des deux listes sont identiques.

  11. 11
    DéfiChoisis la lecture sûre

    Un paramètre est de type List<? super Integer>. Son premier élément peut-il être affecté directement à une variable Integer ? Quel type est garanti à la place ?

    ---reveal---

    Seul Object est garanti. La liste effective peut être une List<Number> ou une List<Object> et contenir des valeurs existantes autres que des entiers. Ajouter un entier par ce type est sûr, mais cette autorisation d’écriture ne décrit pas tous les éléments déjà stockés.

  12. 12
    ExplicationDistingue les vérifications des classes à l’exécution

    L’effacement remplace les paramètres de type par leurs bornes, en utilisant Object lorsqu’aucune borne n’est indiquée. Java a déjà vérifié les utilisations génériques et insère les transtypages nécessaires lors des lectures.

    Une Box<String> et une Box<Integer> partagent la même classe Box à l’exécution. Il n’existe pas de classe distincte pour une boîte d’entiers qu’un test à l’exécution pourrait identifier. La généricité préserve les relations entre types lors de la vérification du code source, ce qui explique pourquoi elle supprime de nombreux transtypages visibles sans spécialiser la classe pour chaque argument.

  13. 13
    ExempleFournis le comportement de création

    Un paramètre de type ne peut pas être instancié avec new T(). Demande plutôt une fabrique au code appelant :

    static <T> T create(Supplier<T> factory) {
        return factory.get();
    }
    List<String> xs = create(ArrayList::new);
    

    La référence choisit un constructeur de liste par l’intermédiaire du type fournisseur cible. Les tableaux génériques tels que new T[10] sont également rejetés ; une collection telle que ArrayList<T> convient généralement au stockage.

  14. 14
    ConseilSuis l’avertissement de type brut

    Une List brute peut accepter "text" et 42 avec des avertissements d’appel non vérifié. Un transtypage ultérieur (String) échoue lorsqu’il atteint 42.

    Remplacer la déclaration par List<String> déplace le problème à l’ajout incorrect. Un avertissement de type non vérifié signifie que le compilateur a perdu une garantie ; l’exécution peut tout de même réussir pour certaines données. Examine l’utilisation du type brut ou le transtypage qui a provoqué l’avertissement, car la lecture qui échoue finalement peut se trouver bien plus loin.

  15. 15
    DéfiVérifie la surcharge après effacement

    Une classe déclare show(Set<String> xs) et show(Set<Integer> xs). Pourquoi Java ne peut-il pas utiliser les arguments de type des éléments pour choisir une surcharge ?

    ---reveal---

    Les deux types de paramètres deviennent Set après effacement ; les déclarations ont donc la même signature après effacement. Donne-leur des noms distincts ou utilise une opération commune. Un test à l’exécution d’un Object contre List<String> présente une limite apparentée : teste List<?> sans affirmer quel est son argument de type pour les éléments.

  16. 16
    DéfiExamine une API de transfert réutilisable

    Une API utilise une liste source brute, transtype chaque élément en Number et l’ajoute à une liste de nombres. Elle doit accepter des entiers et des nombres à virgule flottante comme sources. Quel paramètre source supprime ce transtypage tout en préservant des lectures sûres de nombres ?

    ---reveal---

    Utilise List<? extends Number>. Chaque élément peut être lu comme un Number, et les listes d’entiers comme de nombres à virgule flottante conviennent. List<Number> refuserait ces arguments de liste plus restreints. La destination doit être modifiable ; son seul type générique ne peut pas garantir que les écritures réussiront.

  17. 17
    RécapitulatifLis une API à partir de ses garanties

    Lorsque tu examines une déclaration générique, repère où chaque paramètre de type est introduit, quelles positions il relie et quelles opérations sa borne fournit.

    Pour un transfert, identifie la source comme producteur et la destination comme consommateur. Pour une étape de création, cherche le comportement de construction fourni. Pour un avertissement ou une opération rejetée, demande-toi si les informations génériques ont été contournées ou si l’opération nécessiterait de connaître un type d’élément à l’exécution.

2

Transforme un comportement en argument

14 étapes
Construis et combine des règles fonctionnelles, puis interprète correctement les variables capturées et les références de méthodes.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionCommence par la forme du comportement
  2. 2
    ExplicationTrouve la méthode à implémenter
  3. 3
    ExempleUtilise un bloc lorsqu’une décision le nécessite
  4. 4
    DéfiCorrige le résultat manquant
  5. 5
    ExplicationAssocie quatre tâches courantes à leurs interfaces
  6. 6
    ExempleObserve l’ordre de composition
  7. 7
    LienLaisse les collections exécuter la règle
  8. 8
    DéfiDistingue la suppression de la sélection
  9. 9
    ExplicationCapture une valeur qui n’est pas réaffectée
  10. 10
    ExempleIdentifie l’objet receveur dans la référence
  11. 11
    DéfiVérifie ce que final protège
  12. 12
    ExempleCompte avec une règle de mise à jour de Map
  13. 13
    DéfiCombine type cible et capture
  14. 14
    RécapitulatifSuis un appel à travers la règle
3

Construis un ordre décision par décision

12 étapes
Suis les signes de comparaison, les égalités et les inversions avant d’utiliser un comparateur sur des listes ou des entrées de Map.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionNomme l’ordre nécessaire à la tâche
  2. 2
    ExplicationLis un résultat de comparaison
  3. 3
    ExempleÉvite la soustraction dans les comparaisons numériques
  4. 4
    ExempleConserve l’ordre initial des scores égaux
  5. 5
    DéfiDistingue l’ordre naturel de l’ordre propre à une tâche
  6. 6
    ExplicationAjoute une règle uniquement pour les égalités
  7. 7
    Point cléInverse uniquement les décisions voulues
  8. 8
    ConseilGère les valeurs nulles au bon niveau
  9. 9
    DéfiInterprète l’inversion finale
  10. 10
    LienOrdonne les entrées de la Map
  11. 11
    DéfiRends prévisible l’ordre des entrées de Map à égalité
  12. 12
    RécapitulatifVérifie l’arbre des comparaisons
4

Suis les valeurs dans un flux

16 étapes
Suis les valeurs et leurs types dans un pipeline, puis choisis un résultat terminal et gère les valeurs absentes.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionSuis ensemble les valeurs et leurs types
  2. 2
    ExplicationRepère le résultat terminal
  3. 3
    ExempleArrête-toi au premier élément correspondant
  4. 4
    DéfiEffectue un autre calcul
  5. 5
    ExempleSélectionne les étudiants avant de les transformer en noms
  6. 6
    LienPasse aux opérations numériques
  7. 7
    ExempleAplatis avant de supprimer les doublons
  8. 8
    DéfiLis l’ordre des opérations
  9. 9
    ExempleCalcule une somme et une moyenne
  10. 10
    ExplicationDistingue l’absence d’une valeur
  11. 11
    DéfiGère un maximum absent
  12. 12
    Point cléVérifie les tests universels sur une entrée vide
  13. 13
    ExempleChoisis une valeur de départ neutre
  14. 14
    ConseilConvertis avant le dépassement de capacité
  15. 15
    DéfiCombine aplatissement, sélection et résultat
  16. 16
    RécapitulatifVérifie les cas limites du résultat
5

Construis une synthèse de données utile

15 étapes
Choisis les conteneurs de résultats, résous les clés en double et interroge des synthèses par groupe sans mises à jour cachées.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionPars du résultat souhaité
  2. 2
    ExplicationExige explicitement les modifications ultérieures
  3. 3
    ExempleRésous les collisions avec une règle de fusion
  4. 4
    DéfiChoisis une valeur par clé répétée
  5. 5
    LienAssemble les valeurs sous forme de texte
  6. 6
    ExplicationChoisis une clé, puis un résultat de groupe
  7. 7
    ExempleRésume les données dans les groupes d’étudiants
  8. 8
    Point cléConserve les deux groupes d’une partition
  9. 9
    DéfiPrévois un groupe vide
  10. 10
    ExempleCalcule d’abord les moyennes par ville
  11. 11
    ExempleInterroge la synthèse construite
  12. 12
    ConseilRends le déroulement explicite lorsque cela aide
  13. 13
    DéfiProtège la source pendant le traitement
  14. 14
    DéfiTrouve le groupe gagnant sans erreur
  15. 15
    RécapitulatifVérifie l’ensemble de la tâche de traitement de données

Généricité et flux en Java : QCM

50 Questions Pratiques en 5 séries

Série 1 : Utilise les notions de base10 Questions Pratiques

Question 1 sur 10

Une Box<T> générique possède T get() et void set(T value). Pour Box<String> label = new Box<>("north");, quel est le type de résultat déclaré de label.get() ?
  1. Object
  2. String
  3. Box<String>
  4. CharSequence
Afficher la réponseMasquer la réponse
Réponse : B
L’argument de type String remplace le paramètre de classe T dans le résultat déclaré de l’accesseur. Le compilateur considère donc que l’appel renvoie une chaîne, sans transtypage écrit par le code appelant. L’effacement ne transforme pas le type du résultat en Object dans le code source ; la vérification à la compilation utilise toujours l’argument.

Question 2 sur 10

Une classe utilitaire ordinaire a besoin d’une méthode statique qui renvoie le premier élément de n’importe quelle liste non vide, en préservant son type d’élément. Quelle déclaration convient ?
  1. static E first(List<E> xs)
  2. static <E> Object first(List<Object> xs)
  3. static <E> List<E> first(E xs)
  4. static <E> E first(List<E> xs)
Afficher la réponseMasquer la réponse
Réponse : D
La méthode introduit E avant son type de retour et le répète pour les éléments d’entrée et le résultat. Chaque appel possède ainsi son propre argument de type déduit. Écrire simplement E aux positions du paramètre et du retour ne le déclare pas : la version sans <E> échoue donc dans une classe utilitaire ordinaire.

Question 3 sur 10

Une méthode lit une liste de nombres uniquement pour appeler doubleValue() sur chaque élément. Elle doit accepter à la fois List<Integer> et List<Double>. Quel type de paramètre convient ?
  1. List<? extends Number>
  2. List<Number>
  3. List<? super Number>
  4. List<Object>
Afficher la réponseMasquer la réponse
Réponse : A
Un type joker avec extends accepte un sous-type inconnu de Number : chaque élément lu peut donc être traité comme un Number. List<Number> ne peut pas accepter List<Integer>, car les types génériques sont invariants. Un type joker avec super autoriserait des types d’éléments plus larges, mais les lectures ne fourniraient que le type Object.

Question 4 sur 10

Une Function<Integer, Integer> doit doubler son entrée à l’aide d’un corps sous forme de bloc. Quelle expression lambda convient ?
  1. x -> { x * 2; }
  2. x -> { return; }
  3. x -> { return x * 2; }
  4. () -> { return x * 2; }
Afficher la réponseMasquer la réponse
Réponse : C
Un bloc qui renvoie une valeur doit la renvoyer explicitement. return x * 2; fournit le résultat entier requis par la fonction. Une expression lambda dont le corps est une expression pourrait simplement utiliser x -> x * 2, mais l’ajout d’accolades ne conserve pas ce retour automatique.

Question 5 sur 10

Un prédicat doit enregistrer chaque chaîne testée et utiliser le seuil initial, tandis qu’un compteur distinct nommé limit peut continuer à changer. Le code capture les deux variables locales : int limit = 4; List<String> notes = new ArrayList<>(); Predicate<String> p = s -> { notes.add(s); return s.length() > limit; }; limit++;. Quelle correction respecte ces exigences ?
  1. Rendre finale la référence à la liste ; continuer à capturer limit qui change.
  2. Déplacer l’incrémentation avant l’expression lambda ; capturer le même limit.
  3. Capturer un seuil fixe distinct ; conserver la même référence à la liste.
  4. Utiliser une liste non modifiable ; continuer à capturer limit qui change.
Afficher la réponseMasquer la réponse
Réponse : C
L’incrémentation empêche la capture de limit : copie donc sa valeur initiale dans une variable locale distincte effectivement finale et capture ce seuil. La référence notes, qui ne change pas, peut toujours référencer une liste recevant des entrées de journal. Déplacer l’incrémentation plus tôt ne rendrait pas la variable effectivement finale après sa réaffectation.

Question 6 sur 10

Pour un ordre croissant, qu’indique un résultat négatif de compare(a, b) ?
  1. a doit précéder b.
  2. a doit suivre b.
  3. a et b sont des objets égaux.
  4. a est plus petit d’exactement une position.
Afficher la réponseMasquer la réponse
Réponse : A
Le signe de la comparaison détermine l’ordre relatif : un résultat négatif place le premier argument avant le second. Zéro indique une égalité de rang, tandis qu’un résultat positif place le premier argument après le second. La valeur numérique du résultat négatif ne représente ni une distance ni un nombre de positions.

Question 7 sur 10

Un programme construit names.stream().filter(s -> s.length() > 2) et ne fait rien du flux renvoyé. Quand le prédicat est-il appelé ?
  1. Tous les appels ont lieu pendant l’appel de stream().
  2. Tous les appels ont lieu pendant l’appel de filter().
  3. Les appels ont lieu à la prochaine lecture de la liste source.
  4. Aucun appel n’a lieu sans opération terminale.
Afficher la réponseMasquer la réponse
Réponse : D
filter est une opération intermédiaire qui enregistre une étape de traitement paresseuse. Aucune opération terminale ne lance ici le parcours : le prédicat n’est donc pas appelé. Créer un flux ne copie ni ne filtre immédiatement la liste source, et une lecture ultérieure sans rapport de la liste n’exécute pas ce pipeline.

Question 8 sur 10

Un flux non vide contient 2, 4 et 6. Un produit est calculé avec reduce(0, (a, b) -> a * b). Quelle est la cause du résultat incorrect ?
  1. L’accumulateur doit prendre un argument au lieu de deux.
  2. Zéro n’est pas l’élément neutre de la multiplication et maintient l’accumulateur à zéro.
  3. Un flux d’entiers ne peut pas être réduit par multiplication.
  4. La première valeur d’entrée doit toujours servir d’élément neutre explicite.
Afficher la réponseMasquer la réponse
Réponse : B
Multiplier le zéro initial par 2 donne zéro, et chaque multiplication suivante reste égale à zéro. Utilise 1 comme élément neutre afin que l’accumulateur préserve la première valeur, puis construise le produit 48. Un élément neutre est choisi pour l’opération ; il n’est pas pris arbitrairement parmi les valeurs d’entrée.

Question 9 sur 10

Une liste de résultats doit permettre un appel ultérieur à add. Quelle expression terminale fournit explicitement une ArrayList ?
  1. toList()
  2. collect(Collectors.toList())
  3. collect(Collectors.toCollection(ArrayList::new))
  4. collect(Collectors.toSet())
Afficher la réponseMasquer la réponse
Réponse : C
toCollection(ArrayList::new) demande une implémentation de liste connue et modifiable. Stream.toList() est non modifiable, et Collectors.toList() ne garantit pas la possibilité de modifier le résultat, même si une implémentation particulière le permet. Un collecteur d’ensemble change aussi la structure du résultat et la gestion des doublons.

Question 10 sur 10

Un pipeline sur une ArrayList<String> words appelle forEach(w -> words.add(w + "!")). Comment le programme doit-il ajouter sans erreur une copie transformée de chaque mot d’origine ?
  1. Collecter les copies séparément, puis appeler words.addAll(copies).
  2. Ajouter peek(w -> words.add(w + "!")) avant forEach.
  3. Remplacer forEach par map en gardant words.add à l’intérieur.
  4. Placer words dans une autre variable et ajouter par cette référence.
Afficher la réponseMasquer la réponse
Réponse : A
Collecter d’abord les copies laisse le parcours se terminer avant de modifier la liste d’origine. Ajouter des éléments à une ArrayList pendant que son flux la lit perturbe le parcours et peut lever une ConcurrentModificationException. Une autre référence pointe toujours vers la même source ; elle ne sépare pas les étapes de lecture et d’écriture.
Série 2 : Choisis une implémentation sûre10 Questions Pratiques

Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.

Question 1 sur 10

Une Pair<L, R> générique possède des accesseurs qui renvoient L et R. Pour Pair<String, Integer> p = new Pair<>("zone", 4);, quels sont le type de p.getFirst() et la valeur de p.getSecond() + 2 ?
  1. String et 6
  2. Object et 6
  3. String et 42
  4. Integer et 6
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 2 sur 10

Un paramètre est déclaré List<? super Integer> sink. Quel est le type le plus précis garanti pour sink.get(0) ?
  1. Integer
  2. Number
  3. Object
  4. Comparable<Integer>
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 3 sur 10

Une classe déclare à la fois show(Set<Long> xs) et show(Set<String> xs). Pourquoi la compilation échoue-t-elle ?
  1. Chaque méthode générique doit renvoyer son type d’élément.
  2. Set n’autorise qu’un argument de type dans toute une classe.
  3. Les surcharges Java ne peuvent jamais utiliser de paramètres de collection.
  4. Les deux types de paramètres deviennent Set après effacement.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 4 sur 10

Une méthode doit exécuter une action fournie sur chaque chaîne sans recevoir de résultat de cette action. Quelle interface de paramètre convient ?
  1. Supplier<String>
  2. Consumer<String>
  3. Predicate<String>
  4. UnaryOperator<String>
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 5 sur 10

Une classe Badge possède un constructeur Badge(String text). Une méthode accepte Function<String, Badge> factory. Quelle référence crée le même comportement que s -> new Badge(s) ?
  1. Badge::toString
  2. Badge::new
  3. new Badge::new
  4. Badge::Badge
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 6 sur 10

Une liste est ["z", null, "a"]. Elle est triée avec Comparator.nullsLast(Comparator.reverseOrder()). Quel ordre obtient-on ?
  1. [null, z, a]
  2. [a, z, null]
  3. [null, a, z]
  4. [z, a, null]
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 7 sur 10

Une variable de flux s est utilisée pour s.count(), puis pour s.toList(). Comment le programme doit-il obtenir les deux résultats à partir d’une liste réutilisable data ?
  1. Créer un nouveau data.stream() pour chaque opération terminale.
  2. Appeler s.close() entre les opérations terminales.
  3. Stocker s dans une autre variable avant d’appeler count().
  4. Insérer s.limit(data.size()) après l’appel de count().
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 8 sur 10

Quelle liste résulte de Stream.of("mint", "ash", "mint", "elm").distinct().map(String::length).sorted().toList() ?
  1. [3, 4]
  2. [3, 3, 4, 4]
  3. [3, 3, 4]
  4. [4, 3, 3]
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 9 sur 10

Des éléments ont les codes et quantités P=2, Q=6, P=5, Q=1. Le programme doit renvoyer une liste modifiable de codes distincts dans l’ordre croissant. Quel pipeline convient ?
  1. items.stream().map(Item::getCode).distinct().sorted().toList()
  2. items.stream().map(Item::getCode).distinct().sorted(Comparator.reverseOrder()).collect(Collectors.toCollection(ArrayList::new))
  3. items.stream().map(Item::getCode).distinct().sorted().collect(Collectors.toCollection(ArrayList::new))
  4. items.stream().map(Item::getCode).sorted().collect(Collectors.toCollection(ArrayList::new))
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 10 sur 10

Une tâche compare chaque élément d’une liste à celui qui le précède immédiatement et met à jour deux compteurs selon plusieurs conditions. Pourquoi une boucle à indice est-elle un choix raisonnable ?
  1. Elle montre les indices voisins et les mises à jour coordonnées des compteurs.
  2. Elle est indispensable, car les flux ne peuvent pas examiner des positions voisines.
  3. Elle garantit un traitement plus rapide qu’un flux sur la même liste.
  4. Elle permet d’ajouter à la source pendant un parcours for-each ordinaire.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Série 3 : Suis les types et les résultats10 Questions Pratiques

Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.

Question 1 sur 10

Une Box<Integer> contient 12. Le programme affecte int next = box.get() + 3;. Comment ce code peut-il compiler et donner 15 ?
  1. L’accesseur renvoie un int, car les arguments génériques sont primitifs.
  2. L’accesseur renvoie un Object, que le calcul transtype automatiquement.
  3. L’accesseur renvoie un Integer, que le calcul convertit en type primitif.
  4. L’accesseur renvoie un Double, que le calcul convertit automatiquement.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 2 sur 10

Une méthode statique pick appartient à la classe Items et déclare <T> T pick(List<T> xs). Comment écrit-on un argument de type String explicite lors d’un appel ?
  1. Items.<String>pick(words)
  2. Items.pick<String>(words)
  3. <String>Items.pick(words)
  4. Items.String.pick(words)
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 3 sur 10

Dans une méthode générique non bornée, qu’est-ce qui remplace T lors de l’effacement ?
  1. String
  2. Object
  3. Class<T>
  4. Number
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 4 sur 10

Une interface déclare int run(int x), une méthode par défaut reset() et une méthode statique help(). L’expression x -> x + 2 peut-elle l’implémenter ?
  1. Non ; trois méthodes déclarées exigent trois corps lambda.
  2. Oui ; une expression lambda fournit aussi les corps par défaut et statiques.
  3. Non ; une méthode fonctionnelle doit s’appeler apply.
  4. Oui ; seule run est abstraite et doit être implémentée.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 5 sur 10

Une règle réutilisable prend une String et détermine si elle se termine par ".csv". Quelle interface standard convient à la condition attendue par filter ?
  1. Predicate<String>
  2. Consumer<String>
  3. Supplier<Boolean>
  4. Function<String, Boolean>
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 6 sur 10

Les éléments sont Mira (groupe A, 7), Niko (B, 9), Oren (A, 4), Pia (B, 6). Ils sont triés par comparing(Item::getGroup).thenComparingInt(Item::getScore).reversed(). Quel ordre des noms obtient-on ?
  1. [Pia, Niko, Oren, Mira]
  2. [Niko, Pia, Mira, Oren]
  3. [Mira, Oren, Niko, Pia]
  4. [Oren, Mira, Pia, Niko]
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 7 sur 10

Quelle liste résulte de IntStream.rangeClosed(2, 9).skip(3).limit(2).boxed().toList() ?
  1. [2, 3]
  2. [4, 5]
  3. [5, 6]
  4. [7, 8]
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 8 sur 10

Pour Stream.<Integer>empty(), quels sont les résultats d’appels distincts, chacun sur un nouveau flux, à anyMatch(n -> n > 0), allMatch(n -> n > 0) et noneMatch(n -> n > 0), dans cet ordre ?
  1. false, false, true
  2. true, true, false
  3. false, false, false
  4. false, true, true
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 9 sur 10

Un appel à deux arguments toMap(Item::getCode, Item::getCount) rencontre deux éléments de code "R", ayant tous deux un effectif de 3. Que se passe-t-il ?
  1. Il stocke R=3, car les valeurs répétées sont égales.
  2. Il stocke R=6 en additionnant automatiquement les valeurs égales.
  3. Il stocke les deux entrées sous deux copies de la clé R.
  4. Il lève une IllegalStateException pour la clé répétée.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 10 sur 10

Un flux vide est collecté avec partitioningBy(x -> x > 10, Collectors.counting()). Quel résultat est garanti ?
  1. La Map est vide, car aucune fonction de classification ne s’est exécutée.
  2. Les deux clés booléennes sont associées à 0L.
  3. Seule false est présente et associée à 0L.
  4. Les deux clés booléennes sont associées à des valeurs optionnelles vides.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Série 4 : Suis la tâche complète10 Questions Pratiques

Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.

Question 1 sur 10

Une classe générique Slot<T> a besoin d’une fabrique statique qui crée un emplacement typé à partir de n’importe quelle valeur. Quelle déclaration évite d’utiliser le paramètre de la classe dans un contexte statique ?
  1. static Slot<T> of(T value)
  2. static <E> Slot<T> of(E value)
  3. static Slot<Object> of(T value)
  4. static <E> Slot<E> of(E value)
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 2 sur 10

Une méthode transfère des éléments d’un type choisi d’une liste source vers une liste de destination modifiable avec addAll. Elle doit copier des entiers dans une liste de nombres ou une liste d’objets et préserver la sécurité des types. Quelle signature convient ?
  1. <T> void append(List<? extends T> dst, List<? super T> src)
  2. <T> void append(List<T> dst, List<T> src)
  3. <T> void append(List<? super T> dst, List<? extends T> src)
  4. <T> void append(List<Object> dst, List<T> src)
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 3 sur 10

Une interface déclare boolean check(String s) et boolean equals(Object other), sans autre méthode abstraite. Une expression lambda peut-elle l’implémenter, et pourquoi ?
  1. Oui ; la déclaration correspondant à une méthode d’Object n’ajoute rien à implémenter.
  2. Non ; toute méthode abstraite déclarée compte, l’interface en a donc deux.
  3. Oui ; Java fournit toujours une méthode manquante pour toute interface à deux méthodes.
  4. Non ; une méthode fonctionnelle qui renvoie un booléen doit s’appeler test.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 4 sur 10

Quelle expression lambda a le même comportement que la référence non liée String::compareToIgnoreCase lorsqu’elle est utilisée comme Comparator<String> ?
  1. (a, b) -> b.compareToIgnoreCase(a)
  2. (a, b) -> a.compareToIgnoreCase(b)
  3. a -> a.compareToIgnoreCase(a)
  4. (a, b) -> String.compareToIgnoreCase(a, b)
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 5 sur 10

Une liste modifiable contient X avec le score 8, Y avec le score 3, Z avec le score 8 et W avec le score 3, dans cet ordre. Elle est triée uniquement par score croissant avec un comparateur valide. Quel ordre des noms est garanti ?
  1. [Y, W, X, Z]
  2. [W, Y, Z, X]
  3. [Y, W, Z, X]
  4. [X, Z, Y, W]
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 6 sur 10

Un comparateur de mesures de type double utilise (int) (a - b). Que se passe-t-il lorsqu’il compare 3,6 à 3,1 ?
  1. Il indique correctement que la première valeur est plus grande.
  2. Il indique correctement que la première valeur est plus petite.
  3. Il indique une égalité alors que la première valeur est plus grande.
  4. Il lève une exception à cause du résultat décimal.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 7 sur 10

Quel est le type de résultat de List.of(List.of("red"), List.of("blue", "gold")).stream().map(List::stream) ?
  1. Stream<String>
  2. Stream<Stream<String>>
  3. Stream<List<String>>
  4. List<Stream<String>>
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 8 sur 10

Une méthode renvoie le plus grand entier d’une liste quelconque, ou -1 lorsque la liste est vide. Quelle expression répond à cette exigence ?
  1. xs.stream().reduce(-1, Integer::max)
  2. xs.stream().max(Integer::compare).get()
  3. xs.stream().findFirst().orElse(-1)
  4. xs.stream().max(Integer::compare).orElse(-1)
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 9 sur 10

Les mots sont "reed", "oak", "fern", "elm", "cedar". Après regroupement par longueur avec counting() dans une TreeMap, quelle Map s’affiche ?
  1. {3=1, 4=2, 5=2}
  2. {3=2, 4=1, 5=1}
  3. {3=2, 4=2, 5=1}
  4. {2=3, 1=5}
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 10 sur 10

Voici les relevés : Delta 6 °C, Delta 18 °C, Cove 14 °C et Cove 16 °C. La tâche demande les villes dont la moyenne dépasse 13 °C. Quel résultat est correct ?
  1. Seule Cove satisfait la condition.
  2. Cove et Delta satisfont la condition.
  3. Seule Delta satisfait la condition.
  4. Aucune ville ne satisfait la condition.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Série 5 : Transpose les idées à de nouvelles tâches10 Questions Pratiques

Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.

Question 1 sur 10

Une méthode possède les paramètres List<? super Integer> dst, List<? extends Number> src et essaie dst.add(src.get(0)). Elle doit accepter des sources d’entiers ou de valeurs double et des destinations modifiables de nombres ou d’objets. Comment faut-il modifier ses paramètres ?
  1. List<? super Integer> dst, List<? extends Integer> src
  2. List<? super Number> dst, List<? extends Number> src
  3. List<Number> dst, List<? extends Number> src
  4. List<? extends Number> dst, List<? super Number> src
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 2 sur 10

Une fabrique générique doit créer une nouvelle valeur du type référence choisi par son appelant, sans supposer l’existence d’un constructeur sans argument. Quelle conception convient ?
  1. Utiliser new T() dans une méthode déclarant <T>.
  2. Utiliser new Object() et le renvoyer directement comme T.
  3. Accepter un Supplier<T> et appeler sa méthode get().
  4. Utiliser T.class pour trouver le constructeur de l’appelant.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 3 sur 10

Pour Function<Integer, Integer> triple = x -> x * 3; et shift = x -> x + 2;, que renvoie triple.andThen(shift).apply(4) ?
  1. 18
  2. 12
  3. 20
  4. 14
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 4 sur 10

Une HashMap<String, Integer> contient oak=6, ash=6, elm=2. Quel comparateur d’entrées garantit des valeurs décroissantes et des clés croissantes en cas d’égalité ?
  1. Map.Entry.<String, Integer>comparingByValue().reversed().thenComparing(Map.Entry.comparingByKey())
  2. Map.Entry.<String, Integer>comparingByValue().thenComparing(Map.Entry.comparingByKey()).reversed()
  3. Map.Entry.<String, Integer>comparingByKey().thenComparing(Map.Entry.comparingByValue())
  4. Map.Entry.<String, Integer>comparingByValue().reversed()
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 5 sur 10

Les tickets sont Ivo (2 ; 5), June (1 ; 7), Kai (1 ; 4), Luz (2 ; 5), chaque paire indiquant le groupe et le score. Quel ordre résulte d’un tri par groupe croissant, puis par score décroissant, puis par nom croissant ?
  1. [Kai, June, Ivo, Luz]
  2. [Ivo, Luz, June, Kai]
  3. [June, Kai, Ivo, Luz]
  4. [June, Kai, Luz, Ivo]
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 6 sur 10

Que produit ce code et quels mots sont testés par le filtre ? Stream.of("ash", "cedar", "elm", "pine").filter(w -> w.length() > 3).map(String::length).findFirst()
  1. Optional[5] ; ash et cedar sont testés.
  2. Optional[4] ; les quatre mots sont testés.
  3. Optional[5] ; les quatre mots sont testés.
  4. Optional[3] ; seul ash est testé.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 7 sur 10

Une liste contient les groupes imbriqués [2, 5], [] et [5, 8]. Que renvoie groups.stream().flatMap(List::stream).distinct().filter(n -> n > 3).mapToInt(n -> n).sum() ?
  1. 18
  2. 15
  3. 20
  4. 13
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 8 sur 10

Les valeurs sont -9, -4 et -12. Que renvoie values.stream().filter(n -> n > -8).max(Integer::compare).map(n -> n * 2).orElse(0) ?
  1. 0
  2. -8
  3. -4
  4. -18
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 9 sur 10

Une Sale possède une équipe et un nombre entier d’unités. Ignore les ventes dont le nombre d’unités est nul ou négatif, calcule la moyenne de chaque équipe à partir de ses ventes restantes et renvoie l’équipe ayant la moyenne la plus élevée ou « none » si l’entrée est vide. On suppose qu’aucune moyenne n’est à égalité. Quel pipeline convient ? Les méthodes de collecteurs sont importées statiquement.
  1. sales.stream().filter(s -> s.getUnits() > 0).collect(groupingBy(Sale::getTeam, summingInt(Sale::getUnits))).entrySet().stream().max(Map.Entry.comparingByValue()).map(Map.Entry::getKey).orElse("none")
  2. sales.stream().filter(s -> s.getUnits() > 0).collect(groupingBy(Sale::getTeam, averagingInt(Sale::getUnits))).entrySet().stream().max(Map.Entry.comparingByValue()).map(Map.Entry::getKey).orElse("none")
  3. sales.stream().collect(groupingBy(Sale::getTeam, averagingInt(Sale::getUnits))).entrySet().stream().filter(e -> e.getValue() > 0).max(Map.Entry.comparingByValue()).map(Map.Entry::getKey).orElse("none")
  4. sales.stream().filter(s -> s.getUnits() > 0).collect(groupingBy(Sale::getTeam, averagingInt(Sale::getUnits))).entrySet().stream().max(Map.Entry.comparingByKey()).map(Map.Entry::getKey).orElse("none")
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 10 sur 10

Pour les mots "an", "oak", "to", "elm", quelle est la valeur du groupe vrai obtenue avec partitioningBy(w -> w.length() == 2, mapping(String::toUpperCase, toList())) ?
  1. [AN, TO]
  2. [OAK, ELM]
  3. [AN, TO, ELM]
  4. [AN, OAK, TO, ELM]
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Tu bloques sur ton propre exercice ? Aide aux Devoirs

Mots-Clés

87 Mots-Clés, chacun avec sa définition

A

Argument de type
Un argument de type est le type référence effectif fourni lors de l’utilisation d’une classe, d’une interface ou d’une méthode générique.
L’argument de type String fait que l’accesseur d’une Box<String> renvoie du texte sans transtypage.
Associativité
L’associativité signifie que regrouper autrement les entrées d’une opération ne change pas son résultat, comme l’exige l’accumulateur d’une réduction de flux.
L’associativité rend l’addition adaptée à une réduction d’entiers, tandis que la soustraction ne convient pas.
Autoboxing
L’autoboxing est la conversion automatique par Java d’une valeur primitive en son objet enveloppe correspondant lorsqu’une valeur de type référence est nécessaire.
L’autoboxing permet de transmettre le littéral 5 à un constructeur qui attend un Integer.
Avertissement de type non vérifié
Un avertissement de type non vérifié signale une opération dont le compilateur ne peut pas établir complètement la sécurité des types génériques, souvent à cause d’une utilisation brute ou d’un transtypage.
Un avertissement de type non vérifié sur add d’une liste brute indique une possible lecture ultérieure de type incorrect.

B

BinaryOperator
Un BinaryOperator est une fonction qui combine deux valeurs de même type en un résultat unique de ce même type.
Un BinaryOperator<Integer> tel que Integer::sum peut fusionner les effectifs d’une clé de Map répétée.
Borne supérieure
Une borne supérieure restreint un paramètre de type à un type nommé et à ses sous-types, rendant les opérations de ce type disponibles dans le code générique.
La borne supérieure dans <T extends Number> permet d’appeler doubleValue() sur un T.
Bornes multiples
Les bornes multiples combinent des exigences avec & : un argument de type doit satisfaire chaque borne, une éventuelle classe étant indiquée en premier.
Les bornes multiples dans <T extends Number & Comparable<T>> exigent des comportements numériques et de comparaison.
Boucle à indice
Une boucle à indice utilise une variable de position explicite pour accéder aux éléments d’une séquence, rendant visibles les positions voisines et les décisions de contrôle.
Une boucle à indice peut comparer list.get(i) à list.get(i - 1) tout en mettant à jour deux compteurs.

C

Capture de variable
La capture de variable se produit lorsqu’une expression lambda utilise une variable locale du code environnant, sous réserve des restrictions de Java sur les variables finales.
La capture de variable permet à un prédicat de comparer chaque entrée à une valeur limit environnante.
Classe enveloppe
Une classe enveloppe est un type référence qui représente une valeur primitive et permet ainsi d’utiliser des valeurs comme les entiers comme arguments génériques.
La classe enveloppe Integer rend List<Integer> valide, tandis que List<int> est rejeté.
Classe générique
Une classe générique déclare un ou plusieurs paramètres de type afin que ses champs et ses méthodes puissent préserver des relations entre types choisis par le code appelant.
La classe générique Box<T> permet de créer une boîte de chaînes et une boîte d’entiers à partir d’une seule déclaration.
Clé en double
Une clé en double apparaît lorsque différents éléments d’entrée produisent des clés de Map égales, ce qui nécessite une règle pour déterminer l’unique valeur stockée.
Une clé en double fait échouer toMap à deux arguments même lorsque les deux valeurs associées sont 85.
Collecteur
Un collecteur décrit comment les éléments d’un flux sont rassemblés ou résumés en un résultat, par exemple une liste, une chaîne, une Map ou un calcul par groupe.
Un collecteur peut construire directement la liste de résultats au lieu de modifier une liste extérieure dans forEach.
Collecteur en aval
Un collecteur en aval rassemble ou résume les éléments de chaque groupe ou partition et détermine le type de valeur de la Map.
Le collecteur en aval counting() transforme une Map de listes par groupe en une Map d’effectifs de type Long.
Comparateur
Un comparateur est une règle d’ordre distincte dont la méthode compare renvoie un résultat négatif, nul ou positif pour deux valeurs d’entrée.
Un Comparator<String> peut ordonner des mots par longueur sans changer leur ordre naturel de chaînes.
Comparateur prenant en charge les valeurs nulles
Un comparateur prenant en charge les valeurs nulles donne une place explicite aux valeurs nulles, généralement en encadrant un ordre pour les valeurs non nulles avec nullsFirst ou nullsLast.
Un comparateur de noms prenant en charge les valeurs nulles gère le cas d’un étudiant existant dont le nom extrait est null.
Composition de fonctions
La composition de fonctions relie des transformations de sorte que l’une reçoive le résultat de l’autre ; andThen et compose indiquent des ordres d’appel opposés.
La composition de fonctions double puis incrémente dans twice.andThen(inc), ce qui donne 11 à partir de 5.
Consumer
Un Consumer est une interface fonctionnelle standard pour une action sur une entrée ; sa méthode accept ne renvoie aucune valeur.
Un Consumer<String> peut afficher chaque mot lorsqu’il est transmis à forEach d’une collection.
Contexte statique
Un contexte statique appartient à la classe elle-même : il ne peut donc pas utiliser un paramètre de type lié aux instances paramétrées de cette classe.
Dans un contexte statique, une fabrique de boîtes introduit son propre <E> au lieu d’utiliser le T de la classe.
Corps sous forme d’expression
Un corps sous forme d’expression est un corps d’expression lambda écrit comme une seule expression dont la valeur fournit le résultat requis sans retour explicite.
Le corps sous forme d’expression x + 1 permet à x -> x + 1 de renvoyer directement l’entier incrémenté.
Corps sous forme de bloc
Un corps sous forme de bloc est un corps d’expression lambda entouré d’accolades ; lorsqu’un résultat est requis, ses chemins doivent renvoyer explicitement des valeurs.
Un corps sous forme de bloc utilise return a; et return b; pour implémenter une opération de maximum.
Court-circuit
Le court-circuit permet à une opération de se terminer dès que son résultat est connu, sans nécessairement traiter chaque élément de la source.
Le court-circuit permet à anyMatch de renvoyer vrai dès qu’un élément satisfait son prédicat.
Critère de départage
Un critère de départage est une règle de comparaison supplémentaire utilisée uniquement lorsque toutes les règles précédentes classent les deux éléments à égalité.
Un critère de départage par nom place Ada avant Chen après que leurs scores égaux ont laissé la première comparaison à égalité.

D

Dépassement de capacité d’un entier
Un dépassement de capacité d’un entier se produit lorsqu’un calcul entier dépasse la plage représentable et que le résultat reboucle, ce qui peut inverser le signe d’une comparaison.
Le dépassement de capacité d’un entier rend dangereuse la soustraction de grands entiers opposés pour implémenter un comparateur.
distinct
L’opération distinct supprime les éléments en double d’un flux selon l’égalité des objets, en conservant l’ordre de rencontre pour un flux ordonné.
Appliquer distinct avant de transformer des mots en longueurs peut laisser des longueurs répétées dans le résultat.

E

Effacement des types
L’effacement des types remplace les paramètres de type génériques par leurs bornes ou par Object après vérification ; le compilateur insère les transtypages nécessaires.
L’effacement des types fait que les boîtes de chaînes et d’entiers partagent la même classe Box à l’exécution.
Effectivement final
Une variable locale effectivement finale reçoit une valeur une seule fois sans réaffectation ultérieure, ce qui permet à une expression lambda de la capturer sans le mot-clé final.
Une référence de liste effectivement finale peut toujours référencer une liste à laquelle des éléments sont ajoutés.
Effet de bord
Un effet de bord est une modification extérieure à la valeur renvoyée par un calcul, par exemple la modification d’une liste capturée ou d’un champ de l’objet environnant.
Un effet de bord caché dans un pipeline peut rendre le résultat dépendant de mises à jour extérieures à ses valeurs d’entrée.
Entrée de Map
Une entrée de Map est une paire clé-valeur vue comme un seul objet, ce qui permet de traiter les associations d’une Map dans un flux et de les comparer par clé ou par valeur.
Un comparateur d’entrées de Map peut placer la plus grande quantité en stock en premier sans réordonner la Map source.
Expression lambda
Une expression lambda fournit les paramètres et le corps de la méthode abstraite d’une interface fonctionnelle, avec la flèche -> entre les deux.
L’expression lambda (a, b) -> a * b fournit la règle de multiplication d’une opération.
Extracteur de clé
Un extracteur de clé est une fonction qui sélectionne la valeur utilisée pour comparer un objet, par exemple son nom, son groupe ou son score numérique.
L’extracteur de clé Student::getPoints construit un comparateur numérique d’étudiants avec comparingInt.

É

Élément neutre
Un élément neutre est une valeur qui laisse une autre valeur inchangée sous l’opération d’une réduction, comme zéro pour l’addition ou un pour la multiplication.
L’élément neutre 1 permet à une réduction par produit de préserver son premier facteur au lieu de forcer le résultat à zéro.
Évaluation paresseuse
L’évaluation paresseuse reporte le traitement jusqu’à ce qu’un résultat le nécessite et permet à un flux de ne consommer que le nombre d’éléments requis.
L’évaluation paresseuse permet à findFirst de s’arrêter dès qu’un mot satisfaisant la condition a été trouvé et transformé.

F

Fabrique de collections
Une fabrique de collections fournit à un collecteur une implémentation précise de collection afin que le résultat possède le comportement ou l’ordre nécessaire.
La fabrique de collections ArrayList::new demande explicitement une liste qui autorise les ajouts ultérieurs.
filter
L’opération filter conserve les éléments du flux dont le prédicat est vrai, en préservant leur type et en écartant les autres valeurs.
Un filter sur les points doit s’exécuter avant la transformation des étudiants en noms s’il a besoin de l’accesseur de points.
flatMap
L’opération flatMap transforme chaque entrée en un flux et combine les éléments de ces flux en une seule séquence de sortie.
Un flatMap utilisant List::stream transforme des listes d’entiers imbriquées en un seul flux d’entiers.
Flux
Un flux est une séquence issue d’une source et traitée par des opérations ; il ne stocke pas une collection distincte d’éléments à chaque étape.
Un flux issu de la liste points peut compter les valeurs qui satisfont un test tout en laissant la liste inchangée.
Fonction de classification
Une fonction de classification attribue une clé de regroupement à chaque élément d’entrée et détermine ainsi l’entrée de Map qui le reçoit.
La fonction de classification String::length place tous les mots de quatre lettres sous la même clé entière.
Fonction de fusion
Une fonction de fusion combine deux valeurs associées à la même clé en l’unique valeur qu’une Map doit conserver pour cette clé.
La fonction de fusion Math::max conserve la plus grande quantité lorsqu’une clé de stock apparaît deux fois.
Function
Une Function est une interface fonctionnelle standard qui transforme une entrée de type T en un résultat de type R par apply.
Une Function<String, Integer> peut transformer chaque mot en sa longueur avant le regroupement.

G

groupingBy
Le collecteur groupingBy construit une Map associant les clés rencontrées produites par une fonction de classification à des listes ou à des synthèses en aval des éléments correspondants.
Un collecteur groupingBy avec averagingInt stocke une moyenne de type double par groupe d’étudiants.

I

Inférence de types
L’inférence de types est le processus par lequel le compilateur détermine les informations de type omises à partir des arguments et du contexte cible environnant.
L’inférence de types permet à last(words) de produire une String lorsque words est une liste de chaînes.
Interface fonctionnelle
Une interface fonctionnelle possède une seule méthode abstraite à implémenter, sans compter les déclarations correspondant à des méthodes d’instance publiques d’Object.
Une interface fonctionnelle telle que Operation permet au code appelant de fournir son calcul avec une expression lambda.
Interface générique
Une interface générique déclare des paramètres de type qui relient les types utilisés dans le contrat de ses méthodes à travers différentes implémentations.
Une interface générique Container<T> peut garder l’entrée de put liée au résultat de take.
IntStream
Un IntStream est un flux de valeurs primitives int doté d’opérations numériques telles que la somme et la moyenne, ainsi que de conversions vers des objets.
Un IntStream issu de mapToInt peut additionner des points sans d’abord collecter de valeurs enveloppées.
Invariance
L’invariance signifie que deux utilisations d’un type générique avec des arguments différents n’acquièrent pas de relation de sous-typage simplement parce que leurs arguments de type en ont une.
L’invariance empêche d’utiliser List<Integer> comme List<Number>, ce qui la protège contre l’écriture de valeurs double.
Inversion d’un comparateur
Une inversion d’un comparateur inverse tout l’ordre représenté par le comparateur avant l’appel de reversed(), y compris les critères de départage existants.
Une inversion de comparateur à la fin d’une chaîne par groupe et par score inverse à la fois l’ordre des groupes et celui des scores.

J

joining
Le collecteur joining combine des séquences de caractères en une seule chaîne, avec éventuellement du texte entre les éléments et autour du résultat entier.
Un collecteur joining avec une virgule comme séparateur ne produit pas de virgule après le dernier nom.

L

limit
L’opération limit conserve au maximum le nombre demandé d’éléments au début d’un flux ordonné, ce qui permet d’ignorer les entrées ultérieures.
Un limit avant le tri sélectionne des éléments au début de la séquence d’origine, pas les valeurs les plus élevées de l’ensemble.
Liste non modifiable
Une liste non modifiable rejette les opérations qui changent ses éléments ou sa structure par son intermédiaire, sans rendre immuables les objets qu’elle contient.
La liste non modifiable issue de Stream.toList() lève une exception si le code essaie d’y ajouter un élément.

M

map
L’opération map remplace chaque élément d’entrée par un résultat calculé ; elle peut donc changer le type des éléments du flux.
Un map utilisant String::length transforme des mots en valeurs entières enveloppées, pas en flux de type primitif.
mapping
Le collecteur mapping transforme des éléments avant de transmettre leurs résultats à un autre collecteur, souvent dans un groupe ou une partition.
Un collecteur mapping peut extraire les noms des étudiants dans chaque groupe avant de les collecter dans une liste.
Méthode générique
Une méthode générique déclare ses propres paramètres de type avant son type de retour, ce qui permet à chaque appel de choisir des types d’entrée et de résultat liés.
La méthode générique last renvoie une chaîne pour une liste de chaînes et un entier pour une liste d’entiers.

N

Non-interférence
La non-interférence signifie que le comportement d’un pipeline laisse sa source inchangée pendant son traitement, afin de préserver la validité du parcours.
La non-interférence est rompue lorsqu’une action de flux ajoute des éléments à son ArrayList source.

O

Opérateur diamant
L’opérateur diamant est <> dans un appel de constructeur générique ; il demande au compilateur de déduire les arguments de type tout en conservant les vérifications de type.
L’opérateur diamant dans new ArrayList<>() garde le type de la liste déduit de son contexte.
Opération de vérification d’un prédicat
Une opération de vérification d’un prédicat indique si au moins un, tous ou aucun des éléments d’un flux satisfont un prédicat et peut s’arrêter dès que la réponse est connue.
Sur une entrée vide, une opération de vérification d’un prédicat donne faux pour anyMatch, mais vrai pour allMatch.
Opération intermédiaire
Une opération intermédiaire ajoute une étape de traitement paresseuse et renvoie un autre flux, ce qui permet de sélectionner ou de transformer des éléments avant le résultat final.
L’opération intermédiaire filter attend une opération terminale avant de tester les éléments.
Opération terminale
Une opération terminale termine un pipeline de flux en produisant un résultat ou en effectuant une action, et consomme le flux pour ce calcul.
L’opération terminale count renvoie un long et empêche de réutiliser le même flux.
Optional
Un Optional est un conteneur qui représente une valeur ou aucune, ce qui permet à une méthode d’exprimer un résultat potentiellement absent.
Un Optional issu d’un maximum après filtrage peut être vide ; une valeur de repli gère donc le cas absent.
OptionalDouble
Un OptionalDouble représente une valeur primitive double qui peut être absente, comme la moyenne d’un flux numérique vide.
Un OptionalDouble issu de average() peut fournir la valeur de repli choisie 0,0 grâce à orElse.
Ordre de rencontre
Un ordre de rencontre est la séquence définie dans laquelle une source présente ses éléments, comme l’ordre d’une liste ou l’ordre des clés d’une Map triée.
L’ordre de rencontre conduit findFirst à choisir le premier nom correspondant dans une liste source.
Ordre naturel
Un ordre naturel est l’ordre qu’une classe définit par Comparable.compareTo et qui est utilisé lorsqu’aucun comparateur distinct n’est fourni.
L’ordre naturel des chaînes place le mot avec majuscule "Cherry" avant le mot en minuscules "apple".

P

Paramètre de type
Un paramètre de type est un nom introduit dans une déclaration générique pour représenter un type fourni par le code qui l’utilise.
Le paramètre de type T donne au champ et à l’accesseur d’une boîte le même type choisi par le code appelant.
partitioningBy
Le collecteur partitioningBy répartit les éléments selon un prédicat et renvoie toujours les deux clés booléennes, même si l’un des groupes est vide.
Un collecteur partitioningBy peut renvoyer une liste true vide lorsqu’aucun étudiant n’atteint le seuil testé par son prédicat.
peek
L’opération peek transmet les éléments sans les modifier tout en exécutant une action d’inspection pour les valeurs parcourues ; ses appels peuvent être ignorés.
Un peek utilisé pour afficher des valeurs n’est pas fiable pour un travail indispensable, car l’exécution peut omettre son étape.
Pipeline
Un pipeline relie une source, zéro ou plusieurs opérations intermédiaires et une opération terminale qui produit un résultat ou effectue une action.
Le pipeline qui va de stream à count en passant par filter produit un nombre d’éléments correspondants de type long.
Predicate
Un Predicate est une interface fonctionnelle standard pour une condition, qui prend une valeur et renvoie un boolean par test.
Un Predicate<String> transmis à removeIf désigne les mots correspondants à supprimer.

R

Réduction
Une réduction combine les éléments d’un flux en un seul résultat à l’aide d’un accumulateur, éventuellement à partir d’un élément neutre.
Une réduction par addition avec zéro calcule un total sans mettre à jour un compteur local extérieur.
Référence de constructeur
Une référence de constructeur utilise ClassName::new pour fournir un comportement de création d’objet, avec des arguments déterminés par l’interface cible.
La référence de constructeur Student::new peut transformer un nom en étudiant grâce à un constructeur correspondant.
Référence de méthode
Une référence de méthode désigne un comportement existant de méthode ou de constructeur avec ::, en s’adaptant aux entrées et au résultat d’une interface fonctionnelle cible.
La référence de méthode Integer::parseInt fournit un comportement d’analyse à une fonction qui transforme une chaîne en entier.
Référence de méthode liée
Une référence de méthode liée désigne une méthode d’instance sur un objet receveur particulier, fixé lorsque la référence est évaluée.
La référence de méthode liée System.out::println affiche en utilisant cet objet de sortie particulier.
Référence de méthode non liée
Une référence de méthode non liée désigne une méthode d’instance par sa classe et prend l’objet receveur dans le premier argument de l’interface fonctionnelle.
La référence de méthode non liée String::length appelle length() sur chaque chaîne fournie.
Règle PECS
La règle PECS guide le choix des types jokers : extends pour les sources de valeurs et super pour leurs destinations.
La règle PECS guide le choix des paramètres source et destination d’un transfert entre listes.
Résultat intermédiaire
Un résultat intermédiaire est une valeur nommée entre deux étapes de traitement, par exemple une Map regroupée qu’un pipeline ultérieur interroge.
Un résultat intermédiaire contenant les températures moyennes par ville est filtré après le regroupement pour sélectionner les villes chaudes.

S

Signature après effacement
Une signature après effacement est une signature de méthode dont les types de paramètres sont considérés après effacement, ce qui aide à déterminer si des surcharges entrent en conflit.
La signature après effacement show(Set) est commune aux tentatives de surcharge utilisant Set<String> et Set<Long>.
skip
L’opération skip écarte le nombre demandé d’éléments au début d’un flux ordonné et transmet les éléments restants.
Après skip(2) sur la plage de 1 à 10, bornes incluses, la prochaine valeur émise est 3.
Spécialisation pour un type primitif
Une spécialisation pour un type primitif est une variante d’interface fonctionnelle qui accepte ou renvoie directement des valeurs primitives, sans conversion en objets enveloppes.
La spécialisation pour un type primitif ToIntFunction<String> fournit directement des longueurs à mapToInt.
Supplier
Un Supplier est une interface fonctionnelle standard dont la méthode get sans argument fournit une valeur de son type de résultat.
Un Supplier<List<String>> utilisant ArrayList::new peut fournir une nouvelle liste à chaque appel.

T

Témoin de type
Un témoin de type est un argument de type explicite de méthode, écrit entre le point qui suit le receveur et le nom de la méthode lors d’un appel.
Le témoin de type dans Lists.<String>last(words) indique explicitement le type choisi pour la méthode.
Transitivité
La transitivité exige qu’un ordre place a avant c chaque fois qu’il place a avant b et b avant c, ce qui maintient la cohérence des comparaisons.
La transitivité empêche un comparateur de créer un cycle entre trois objets classés.
Tri stable
Un tri stable préserve l’ordre relatif initial des éléments dont la comparaison indique l’égalité selon la règle d’ordre utilisée.
Un tri stable garde Ada avant Chen lorsqu’ils ont tous deux 85 points et qu’Ada apparaissait en premier.
Type brut
Un type brut est une classe ou une interface générique utilisée sans arguments de type, ce qui contourne certaines vérifications imposées par une utilisation paramétrée.
Un type brut dans List xs peut autoriser une écriture incorrecte qu’une List<String> rejetterait.
Type cible
Un type cible est l’interface fonctionnelle attendue qui fournit à une expression lambda ou à une référence de méthode ses types de paramètres et de résultat.
Le type cible Predicate<String> fait de s -> s.isEmpty() un test booléen sur une chaîne.
Type joker à borne inférieure
Un type joker à borne inférieure tel que ? super Integer désigne un type inconnu qui est le type indiqué ou l’un de ses supertypes.
Un type joker à borne inférieure permet à une méthode qui écrit des entiers d’accepter une destination List<Number> modifiable.
Type joker à borne supérieure
Un type joker à borne supérieure tel que ? extends Number désigne un type inconnu qui est le type indiqué ou l’un de ses sous-types.
Un type joker à borne supérieure permet à un calcul de somme de lire aussi bien des listes d’entiers que des listes de valeurs double.
Type joker non borné
Un type joker non borné est ? sans borne ; il représente un argument inconnu et ne permet de lire les éléments que comme des Object.
Un type joker non borné dans List<?> permet à une méthode d’affichage de consulter la taille d’une liste sans connaître son type d’élément.

U

UnaryOperator
Un UnaryOperator est une fonction dont l’unique entrée et le résultat ont le même type, souvent utilisée pour remplacer les éléments d’une collection.
Un UnaryOperator<String> peut mettre chaque mot en majuscules par replaceAll sur une liste modifiable.

À propos de cette unité

Par StudeliSignaler une erreur

Crée ton propre Matériel d'Étude

Transforme tes notes, diapos et documents en Guides d'Étude, Questions Pratiques, Flashcards et 6 autres outils.

Commencer