1. Comptage des éléments : count()
Lorsque vous travaillez avec des collections de nombres ou d’objets, il faut presque toujours calculer quelque chose : le nombre d’éléments, la somme, la moyenne, le maximum ou le minimum. Par exemple, le nombre d’employés dans une entreprise, la somme de tous les salaires, l’âge moyen des étudiants, le produit le plus cher, la commande maximale. Avec l’arrivée de l’API Stream, tout est devenu plus simple et plus expressif.
Comment ça marche
La méthode count() est un opérateur terminal de l’API Stream qui renvoie le nombre d’éléments dans le flux.
long count = employees.stream().count();
Si vous devez compter uniquement les éléments qui satisfont une condition — utilisez le filtre filter(...) :
long richCount = employees.stream()
.filter(e -> e.getSalary() > 100_000)
.count();
Exemple pour notre application :
Supposons qu’il y a une liste de produits :
List<Product> products = List.of(
new Product("Lait", 80),
new Product("Fromage", 250),
new Product("Pain", 40)
);
Comptons combien de produits sont plus chers que 100 :
long expensiveCount = products.stream()
.filter(p -> p.getPrice() > 100)
.count();
System.out.println("Produits chers : " + expensiveCount);
Qu’est-ce que Optional
En Java, il existe un conteneur spécial — Optional. C’est un objet enveloppe qui peut contenir une valeur ou être vide. Il s’utilise lorsque le résultat peut être absent et qu’il faut le signaler explicitement. Par exemple, les méthodes min(), max() et average() peuvent retourner un résultat vide si la collection est vide. Au lieu de null, on renvoie un Optional.
Méthodes principales d’Optional
- isPresent() / isEmpty() — vérification de la présence d’une valeur.
- orElse(valeur) — renvoyer la valeur ou, si vide, la valeur par défaut.
- orElseThrow() — lancer une exception si vide.
- ifPresent(...) — exécuter une action uniquement en présence d’une valeur.
Exemple :
OptionalInt min = products.stream()
.mapToInt(Product::getPrice)
.min();
if (min.isPresent()) {
System.out.println("Prix minimal : " + min.getAsInt());
} else {
System.out.println("Liste vide");
}
Ou plus court :
int minValue = min.orElse(-1);
System.out.println("Prix minimal : " + minValue);
Cette approche permet d’éviter NullPointerException et rend le code plus sûr.
2. Somme, moyenne, minimum et maximum pour les données numériques
Streams primitifs : IntStream, DoubleStream, LongStream
Pour les données numériques, l’API Stream propose des streams spécialisés : IntStream, DoubleStream, LongStream. Ils permettent de calculer facilement et rapidement la somme, la moyenne, le minimum et le maximum.
Conversion vers un stream numérique
Pour obtenir un stream numérique à partir d’un flux d’objets, utilisez les méthodes mapToInt, mapToDouble, mapToLong.
int sum = products.stream()
.mapToInt(Product::getPrice)
.sum();
System.out.println("Somme totale des prix : " + sum);
Méthodes
- sum() — somme de tous les éléments.
- average() — moyenne arithmétique (renvoie un OptionalDouble).
- min(), max() — minimum et maximum (renvoient OptionalInt/OptionalDouble/OptionalLong).
Exemples :
// Somme de tous les prix
int total = products.stream()
.mapToInt(Product::getPrice)
.sum();
// Prix moyen
OptionalDouble avg = products.stream()
.mapToInt(Product::getPrice)
.average();
// Prix minimum et maximum
OptionalInt min = products.stream()
.mapToInt(Product::getPrice)
.min();
OptionalInt max = products.stream()
.mapToInt(Product::getPrice)
.max();
Comment obtenir une valeur à partir d’Optional
double average = avg.orElse(0.0); // 0.0 si la collection est vide
int minValue = min.orElse(-1); // -1 si la collection est vide
int maxValue = max.orElse(-1); // -1 si la collection est vide
Blague de développeur : une liste vide n’est pas une erreur, c’est juste une situation où vous n’avez pas de données. Mais si vous essayez de prendre une valeur d’un Optional vide avec getAsInt(), vous attraperez une NoSuchElementException. Utilisez toujours orElse ou isPresent() !
3. Agrégation d’objets : Collectors.summingInt, averagingInt, maxBy, minBy
Si vous avez un flux d’objets et que vous souhaitez agréger des valeurs selon un critère, utilisez les collecteurs spécialisés de la classe Collectors.
summingInt, averagingInt
int totalSalary = employees.stream()
.collect(Collectors.summingInt(Employee::getSalary));
double avgSalary = employees.stream()
.collect(Collectors.averagingInt(Employee::getSalary));
minBy, maxBy
Pour trouver l’objet avec la valeur maximale/minimale d’un champ :
Optional<Employee> richest = employees.stream()
.collect(Collectors.maxBy(Comparator.comparingInt(Employee::getSalary)));
Optional<Employee> poorest = employees.stream()
.collect(Collectors.minBy(Comparator.comparingInt(Employee::getSalary)));
Important : le résultat est un Optional, car la collection peut être vide.
Exemple
Créons la classe Product :
public class Product {
private final String name;
private final int price;
public Product(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() { return name; }
public int getPrice() { return price; }
}
Créons une liste de produits :
List<Product> products = List.of(
new Product("Lait", 80),
new Product("Fromage", 250),
new Product("Pain", 40)
);
Trouvons le produit le plus cher :
Optional<Product> maxProduct = products.stream()
.collect(Collectors.maxBy(Comparator.comparingInt(Product::getPrice)));
maxProduct.ifPresent(p -> System.out.println("Produit le plus cher : " + p.getName()));
4. Combinaison avec le regroupement
Les méthodes d’agrégation se combinent souvent avec le regroupement. Par exemple, on peut trouver le prix moyen des produits par première lettre du nom :
Map<Character, Double> avgPriceByLetter = products.stream()
.collect(Collectors.groupingBy(
p -> p.getName().charAt(0),
Collectors.averagingInt(Product::getPrice)
));
System.out.println(avgPriceByLetter);
Résultat :
{L=80.0, F=250.0, P=40.0}
5. Exemples concrets
Exemple 1 : Étudiants et notes
Liste des étudiants et leurs scores :
public class Student {
private final String name;
private final int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public int getScore() {
return score;
}
}
List<Student> students = List.of(
new Student("Alice", 90),
new Student("Bob", 75),
new Student("Charlie", 100)
);
Combien d’étudiants ont une note supérieure à 80 ?
long count = students.stream()
.filter(s -> s.getScore() > 80)
.count();
System.out.println("Étudiants avec une note > 80 : " + count);
Note moyenne :
double avg = students.stream()
.mapToInt(Student::getScore)
.average()
.orElse(0.0);
System.out.println("Note moyenne : " + avg);
Étudiant le plus performant :
students.stream()
.max(Comparator.comparingInt(Student::getScore))
.ifPresent(s -> System.out.println("Meilleur étudiant : " + s.getName()));
Exemple 2 : Nombre d’articles uniques
long uniqueCount = products.stream()
.map(Product::getName)
.distinct()
.count();
System.out.println("Articles uniques : " + uniqueCount);
6. Nuances utiles
Schéma visuel : fonctionnement des méthodes d’agrégation
Collection d’objets
│
▼
stream()
│
▼
mapToInt/Double/Long (si nécessaire)
│
▼
sum() / average() / min() / max() / count()
│
▼
Résultat (int, double, long, Optional)
Gestion d’Optional : comment extraire des valeurs en toute sécurité
Pourquoi les méthodes renvoient-elles un Optional ?
Si la collection est vide (par exemple, vous cherchez le maximum d’une liste vide), un Optional vide est renvoyé. Si vous appelez immédiatement getAsInt()/get(), une exception sera levée.
Bonnes pratiques :
- Utiliser orElse(valeur par défaut)
- Utiliser ifPresent(...) pour n’agir qu’en présence d’une valeur
- Utiliser orElseThrow(...) pour lever explicitement une exception
Exemple :
OptionalDouble avg = products.stream()
.mapToInt(Product::getPrice)
.average();
System.out.println("Prix moyen : " + avg.orElse(0.0));
Quand utiliser les streams primitifs, et quand — les collecteurs
- S’il faut simplement calculer la somme/la moyenne/le minimum/le maximum d’un champ numérique — utilisez mapToInt et la méthode correspondante (sum, average, min, max).
- Si vous voulez agréger des objets (par exemple, trouver l’objet avec le champ maximal), utilisez les collecteurs maxBy/minBy.
- S’il faut regrouper et calculer des agrégats par groupes — utilisez la combinaison de groupingBy avec des collecteurs d’agrégation.
7. Erreurs courantes avec les méthodes d’agrégation
Erreur n°1 : Optional non traité. Les débutants oublient souvent que min, max, average renvoient un Optional. En conséquence, lorsqu’on essaie d’obtenir une valeur d’un conteneur vide, une NoSuchElementException se produit. Utilisez toujours orElse, orElseThrow ou ifPresent.
Erreur n°2 : Utiliser un stream ordinaire au lieu de mapToInt/mapToDouble. Si vous écrivez stream().sum(), le compilateur dira : « Cette méthode n’existe pas ! ». Pour la somme, la moyenne, le minimum et le maximum, il faut des streams primitifs.
Erreur n°3 : Incompatibilité de types lors de la comparaison. Lorsque vous cherchez le maximum/minimum parmi des objets, utilisez le bon comparateur : Comparator.comparingInt(Employee::getSalary). Sinon, vous pouvez obtenir une erreur de compilation ou un résultat incorrect.
Erreur n°4 : Comparaison d’objets avec null. Si la collection peut contenir des éléments null, l’agrégation peut lever une NullPointerException. Il vaut mieux filtrer ces éléments au préalable ou utiliser Comparator.nullsLast(...)/nullsFirst(...).
Erreur n°5 : Utiliser sum/average/min/max avec des collections vides sans tenir compte du résultat. Si la collection est vide, la méthode sum() renverra 0, et average()/min()/max() — un Optional vide. N’oubliez pas de gérer ce cas.
GO TO FULL VERSION