Aller au contenu principal

Streams

Les streams en Java sont introduits avec Java 8 et font partie du package java.util.stream. Ils permettent de traiter des collections (comme les listes ou les ensembles) de manière déclarative et souvent parallélisable, en s'inspirant du paradigme fonctionnel.

1. Définition rapide

Un Stream est une séquence d’éléments sur laquelle on peut effectuer une suite d’opérations, comme filtrer, transformer ou réduire, sans modifier la source de données.

2. Caractéristiques des Streams

  • Non modifiant : le Stream ne modifie pas la collection source.
  • Paresseux (lazy) : les opérations intermédiaires ne sont pas exécutées tant qu’une opération terminale n’est pas appelée.
  • Peut être parallèle : exécution possible sur plusieurs threads avec parallelStream().

3. Structure d’un pipeline de Stream

collection.stream() // Création du Stream
.filter(e -> e > 10) // Opération intermédiaire
.map(e -> e * 2) // Opération intermédiaire
.forEach(System.out::println); // Opération terminale

4. Types d’opérations

  • Intermédiaires (retournent un Stream) :
    • filter(), map(), sorted(), distinct(), limit(), skip()
  • Terminales (déclenchent l’exécution) :
    • forEach(), collect(), reduce(), count(), anyMatch(), findFirst()

5. Exemple

List<String> noms = List.of("Alice", "Bob", "Alex", "Charles");

noms.stream()
.filter(nom -> nom.startsWith("A"))
.map(String::toUpperCase)
.sorted()
.forEach(System.out::println);

Résultat :

ALEX
ALICE

6. Cas typique avec collect()

List<Integer> result = List.of(1, 2, 3, 4, 5).stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());

8. Avantages

  • Code plus concis et lisible
  • Meilleure expressivité
  • Possibilité de paralléliser facilement
  • Moins de mutations d’état

Stream primitifs

  • Performance :
    • Les flux ordinaires (Stream<T>) fonctionnent avec des objets. Lorsque vous utilisez des types primitifs avec des flux ordinaires, ils sont automatiquement "emballés" (boxed) dans leurs classes wrapper correspondantes (Integer, Long, Double).
    • L'emballage et le déballage ont un coût en termes de performances. Les flux de types primitifs évitent cet emballage, ce qui peut améliorer considérablement les performances, en particulier pour les opérations numériques.
  • Méthodes spécialisées :
    • Les flux de types primitifs fournissent des méthodes spécialisées pour les opérations numériques, telles que sum(), average(), range(), et rangeClosed(). Ces méthodes sont absentes des flux ordinaires.

Création

IntStream.of(1, 2, 3) // à partir de valeurs
IntStream.range(1, 10) // de 1 à 9 (exclut 10)
IntStream.rangeClosed(1, 10) // de 1 à 10 (inclut 10)
Arrays.stream(new int[]{1, 2, 3}) // à partir d'un tableau

Opérations spécialisées

int somme = IntStream.rangeClosed(1, 100).sum(); // 5050

OptionalDouble moyenne = IntStream.of(10, 20, 30).average(); // 20.0

int max = IntStream.of(5, 2, 8, 1).max().getAsInt(); // 8

int[] tableau = IntStream.range(1, 6).toArray(); // [1, 2, 3, 4, 5]

Conversion vers un Stream d'objets (boxed())

Quand tu as besoin de collecter dans une List, il faut convertir avec boxed() :

List<Integer> liste = IntStream.range(1, 6)
.boxed()
.collect(Collectors.toList()); // [1, 2, 3, 4, 5]

Conversion depuis un Stream d'objets (mapToInt())

List<String> mots = List.of("Java", "est", "cool");

int totalCaracteres = mots.stream()
.mapToInt(String::length)
.sum(); // 11

Résultats optionnels : OptionalInt, OptionalDouble, OptionalLong

Les opérations average(), min(), max() retournent une variante primitive d'Optional car le stream pourrait être vide.

OptionalDouble moyenne = IntStream.of(10, 20, 30).average(); // OptionalDouble[20.0]
OptionalInt max = IntStream.of(5, 2, 8).max(); // OptionalInt[8]

Pour extraire la valeur, on utilise getAsDouble(), getAsInt() ou getAsLong() :

OptionalDouble moyenne = IntStream.of(10, 20, 30).average();

if (moyenne.isPresent()) {
System.out.println(moyenne.getAsDouble()); // 20.0
}

// Ou avec une valeur par défaut si le stream est vide
double resultat = IntStream.empty().average().orElse(0.0); // 0.0

getAsDouble() sur un OptionalDouble vide lance NoSuchElementException. Toujours vérifier avec isPresent() avant, ou utiliser orElse() pour éviter l'exception.


Documentation Oracle : stream() (java.util.stream.Stream) · range, rangeClosed, sum, average, boxed, mapToInt (java.util.stream.IntStream)