1. Méthode distinct : supprimer les doublons
Dans les cas réels, il ne suffit pas souvent de filtrer et de transformer les données : on veut aussi ne sélectionner que des éléments uniques dans une collection, limiter la taille du résultat ou, au contraire, ignorer les quelques premiers éléments. Par exemple :
- Obtenir la liste des noms d’utilisateurs uniques.
- Prendre seulement les 10 premiers enregistrements à afficher sur une page.
- Ignorer les 5 premiers éléments (par exemple, pour une navigation paginée — « afficher à partir de la 2e page »).
Pour de telles tâches, l’API Stream propose des méthodes dédiées : distinct, limit, skip.
Comment fonctionne distinct ?
La méthode distinct() renvoie un nouveau flux dans lequel tous les doublons ont été supprimés. Les doublons sont déterminés à l’aide des méthodes equals et hashCode de la classe correspondante.
Imaginez que vous êtes numismate et qu’il est important qu’il n’y ait pas de doublons dans votre collection principale. Toutes les pièces répétées iront au fonds d’échange. Justement, distinct() sait écarter les doublons de la collection principale.
Exemple : noms d’utilisateurs uniques
List<String> names = List.of(
"Alice", "Bob", "Alice", "Eva", "Bob", "Denis", "Gleb", "Eva"
);
Récupérons la liste des noms uniques :
List<String> uniqueNames = names.stream()
.distinct()
.collect(Collectors.toList());
System.out.println(uniqueNames);
// Sortie : [Alice, Bob, Eva, Denis, Gleb]
Exemple : adresses email uniques des utilisateurs
Supposons que nous ayons une classe User :
public class User {
String name;
String email;
// Constructeur, getters, toString() — pour la commodité
public User(String name, String email) {
this.name = name;
this.email = email;
}
@Override
public String toString() {
return name + " <" + email + ">";
}
}
Liste d’utilisateurs avec des doublons d’email :
List<User> users = List.of(
new User("Alice", "alice@mail.com"),
new User("Bob", "bob@mail.com"),
new User("Eva", "eva@mail.com"),
new User("Alice2", "alice@mail.com"), // doublon d’email !
new User("Gleb", "gleb@mail.com"),
new User("Eva2", "eva@mail.com") // doublon d’email !
);
Si vous appelez simplement users.stream().distinct(), les doublons ne seront pas supprimés, car par défaut les objets User n’ont pas de equals et hashCode redéfinis. Dans ce cas, distinct ne fonctionne que pour les égalités de référence.
Solution : Redéfinir equals et hashCode de manière à ce que l’unicité soit déterminée par email.
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(email, user.email);
}
@Override
public int hashCode() {
return Objects.hash(email);
}
Maintenant :
List<User> uniqueUsers = users.stream()
.distinct()
.collect(Collectors.toList());
uniqueUsers.forEach(System.out::println);
// Alice <alice@mail.com>
// Bob <bob@mail.com>
// Eva <eva@mail.com>
// Gleb <gleb@mail.com>
Important : Pour vos propres classes, redéfinissez toujours equals et hashCode si vous voulez que distinct fonctionne « normalement » !
2. Méthode limit : limiter le nombre d’éléments
La méthode limit(long maxSize) renvoie un nouveau flux qui contient au plus les maxSize premiers éléments du flux d’origine.
Analogie : Vous entrez dans une pâtisserie et souhaitez goûter seulement 3 gâteaux sur 100. limit(3) — on vous en donne exactement trois premiers, les autres — « pas aujourd’hui ».
Exemple : les 3 premiers noms
List<String> firstThree = names.stream()
.limit(3)
.collect(Collectors.toList());
System.out.println(firstThree);
// Sortie : [Alice, Bob, Alice]
Exemple avec notre application : top‑3 des nouveaux utilisateurs
List<User> firstUsers = users.stream()
.limit(3)
.collect(Collectors.toList());
firstUsers.forEach(System.out::println);
// Alice <alice@mail.com>
// Bob <bob@mail.com>
// Eva <eva@mail.com>
Exemple avec tri
On peut combiner avec un tri — par exemple, prendre le top‑2 des utilisateurs avec l’email le plus court :
List<User> top2ShortEmail = users.stream()
.sorted(Comparator.comparingInt(u -> u.email.length()))
.limit(2)
.collect(Collectors.toList());
top2ShortEmail.forEach(System.out::println);
// Bob <bob@mail.com>
// Eva <eva@mail.com>
3. Méthode skip : ignorer les premiers éléments
La méthode skip(long n) renvoie un nouveau flux dans lequel les n premiers éléments du flux d’origine sont ignorés.
Exemple : ignorer les 2 premiers noms
List<String> afterTwo = names.stream()
.skip(2)
.collect(Collectors.toList());
System.out.println(afterTwo);
// Sortie : [Alice, Eva, Bob, Denis, Gleb, Eva]
Exemple : navigation par pages (pagination)
Souvent, sur un site, on affiche par exemple « 3 utilisateurs par page ». Pour la deuxième page, il faut ignorer les 3 premiers, puis prendre les 3 suivants :
int pageSize = 3;
int pageNumber = 2; // Deuxième page
List<User> page = users.stream()
.skip(pageSize * (pageNumber - 1))
.limit(pageSize)
.collect(Collectors.toList());
page.forEach(System.out::println);
// Alice2 <alice@mail.com>
// Gleb <gleb@mail.com>
// Eva2 <eva@mail.com>
4. Combiner distinct, limit, skip
Ces méthodes peuvent (et doivent) être combinées — selon le besoin.
Exemple : obtenir 2 noms uniques, en commençant par le troisième
List<String> result = names.stream()
.distinct() // On supprime les doublons : [Alice, Bob, Eva, Denis, Gleb]
.skip(2) // On saute Alice et Bob : [Eva, Denis, Gleb]
.limit(2) // On n’en prend que deux : [Eva, Denis]
.collect(Collectors.toList());
System.out.println(result);
// Sortie : [Eva, Denis]
Exemple avec filtration
Supposons qu’il faille obtenir les 2 premiers emails uniques qui contiennent la lettre « a » :
List<String> emails = users.stream()
.map(user -> user.email)
.filter(email -> email.contains("a"))
.distinct()
.limit(2)
.collect(Collectors.toList());
System.out.println(emails);
// Sortie : [alice@mail.com, eva@mail.com]
5. Pratique : exercices d’application
Exercice 1. Obtenir la liste des noms d’utilisateurs uniques dont la longueur est supérieure à 3 caractères
List<String> longUniqueNames = names.stream()
.filter(name -> name.length() > 3)
.distinct()
.collect(Collectors.toList());
System.out.println(longUniqueNames);
// Par exemple : [Alice, Denis]
Exercice 2. Obtenir le 3e et le 4e email unique de la liste des utilisateurs
List<String> thirdAndFourthEmail = users.stream()
.map(user -> user.email)
.distinct()
.skip(2)
.limit(2)
.collect(Collectors.toList());
System.out.println(thirdAndFourthEmail);
// Par exemple : [eva@mail.com, gleb@mail.com]
Exercice 3. Obtenir les 5 premiers nombres uniques qui sont supérieurs à 10
List<Integer> numbers = List.of(5, 12, 17, 5, 23, 17, 42, 19, 12, 8);
List<Integer> result = numbers.stream()
.filter(n -> n > 10)
.distinct()
.limit(5)
.collect(Collectors.toList());
System.out.println(result);
// Sortie : [12, 17, 23, 42, 19]
6. Schéma visuel : ordre d’application des opérations
graph TD
A[Liste initiale] --> B[filter]
B --> C[distinct]
C --> D[skip]
D --> E[limit]
E --> F[collect]
Commentaire :
On applique généralement d’abord filter, puis on supprime les doublons avec distinct, ensuite on utilise skip et limit, et à la fin — collect. Mais l’ordre peut être modifié si la tâche l’exige.
7. Erreurs typiques avec distinct, limit, skip
Erreur n° 1 : penser que distinct supprime les doublons selon « n’importe quel » critère.
En réalité, distinct s’appuie sur la méthode equals de l’objet. Si vous voulez l’unicité selon un champ (par exemple uniquement email), et non sur l’objet entier — il faut soit redéfinir equals/hashCode, soit utiliser des astuces avec Collectors.toMap() ou un filtrage supplémentaire.
Erreur n° 2 : mauvais ordre des opérations.
Si vous appliquez d’abord limit puis distinct, des doublons peuvent subsister — vous avez limité le flux aux N premiers éléments, parmi lesquels des éléments identiques peuvent figurer.
Erreur n° 3 : sauter avec skip plus d’éléments qu’il n’y en a.
Si vous essayez d’ignorer plus d’éléments que le flux n’en contient, le résultat sera simplement une liste vide — il n’y aura pas d’erreur, mais le résultat peut surprendre.
Erreur n° 4 : performance non prise en compte.
Les méthodes distinct, limit, skip peuvent être inefficaces sur des flux très volumineux, en particulier si le flux n’est pas ordonné ou si les éléments sont complexes. Dans 99 % des cas courants, ce n’est pas un problème, mais si vous travaillez avec des millions d’enregistrements — il faut y réfléchir.
Erreur n° 5 : oubli de redéfinir la logique equals/hashCode pour vos classes.
Si vous travaillez avec des objets métiers (par exemple, User), sans redéfinition de ces méthodes, distinct considérera les objets comme différents, même s’ils sont « logiquement » identiques.
GO TO FULL VERSION