1. Methode distinct: Duplikate entfernen
In realen Aufgaben muss man Daten nicht nur filtern und transformieren, sondern häufig auch aus einer Sammlung nur eindeutige Elemente auswählen, die Größe des Ergebnisses begrenzen oder im Gegenteil die ersten Elemente überspringen. Zum Beispiel:
- Eine Liste eindeutiger Benutzernamen abrufen.
- Nur die ersten 10 Einträge zur Anzeige auf der Seite nehmen.
- Die ersten 5 Elemente überspringen (z. B. bei der Implementierung der Seitennavigation – „ab Seite 2 anzeigen“).
Für solche Aufgaben gibt es in der Stream‑API spezielle Methoden: distinct, limit, skip.
Wie funktioniert distinct?
Die Methode distinct() gibt einen neuen Stream zurück, in dem alle Duplikate der Elemente entfernt sind. Duplikate werden über die Methoden equals und hashCode der jeweiligen Klasse bestimmt.
Stellen Sie sich vor, Sie sind Numismatiker, und es ist Ihnen wichtig, dass es in der Hauptsammlung keine Duplikate gibt. Alle doppelten Münzen wandern in den Tauschbestand. Genau distinct() kann Duplikate aus der Hauptsammlung aussortieren.
Beispiel: eindeutige Benutzernamen
List<String> names = List.of(
"Alice", "Bob", "Alice", "Eva", "Bob", "Denis", "Gleb", "Eva"
);
Wir erhalten die Liste eindeutiger Namen:
List<String> uniqueNames = names.stream()
.distinct()
.collect(Collectors.toList());
System.out.println(uniqueNames);
// Ausgabe: [Alice, Bob, Eva, Denis, Gleb]
Beispiel: eindeutige Benutzer-E-Mails
Angenommen, wir haben eine Klasse User:
public class User {
String name;
String email;
// Konstruktor, Getter, toString() - zur Bequemlichkeit
public User(String name, String email) {
this.name = name;
this.email = email;
}
@Override
public String toString() {
return name + " <" + email + ">";
}
}
Liste von Benutzern mit doppelten E-Mail-Adressen:
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"), // doppelter E-Mail!
new User("Gleb", "gleb@mail.com"),
new User("Eva2", "eva@mail.com") // doppelter E-Mail!
);
Wenn man einfach users.stream().distinct() aufruft, werden Duplikate nicht entfernt, weil bei User-Objekten standardmäßig die Methoden equals und hashCode nicht überschrieben sind. In diesem Fall arbeitet distinct nur bei identischen Referenzen.
Lösung: equals und hashCode so überschreiben, dass die Eindeutigkeit über email bestimmt wird.
@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);
}
Jetzt:
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>
Wichtig: Überschreiben Sie in eigenen Klassen immer equals und hashCode, wenn Sie möchten, dass distinct „vernünftig“ funktioniert!
2. Methode limit: Anzahl der Elemente begrenzen
Die Methode limit(long maxSize) gibt einen neuen Stream zurück, der höchstens die ersten maxSize Elemente des Ursprungsstreams enthält.
Analogie: Sie kommen in eine Konditorei und möchten nur 3 Törtchen von 100 probieren. limit(3) — und Sie bekommen genau die ersten drei, der Rest – „heute leider nicht“.
Beispiel: die ersten 3 Namen
List<String> firstThree = names.stream()
.limit(3)
.collect(Collectors.toList());
System.out.println(firstThree);
// Ausgabe: [Alice, Bob, Alice]
Beispiel aus unserer Anwendung: Top‑3 neue Benutzer
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>
Beispiel mit Sortierung
Man kann mit einer Sortierung kombinieren – z. B. die Top‑2 Benutzer mit der kürzesten E‑Mail-Adresse nehmen:
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. Methode skip: erste Elemente überspringen
Die Methode skip(long n) gibt einen neuen Stream zurück, in dem die ersten n Elemente des Ursprungsstreams übersprungen werden.
Beispiel: die ersten 2 Namen überspringen
List<String> afterTwo = names.stream()
.skip(2)
.collect(Collectors.toList());
System.out.println(afterTwo);
// Ausgabe: [Alice, Eva, Bob, Denis, Gleb, Eva]
Beispiel: Seitennavigation (Pagination)
Oft muss eine Website z. B. „3 Benutzer pro Seite“ anzeigen. Für die zweite Seite müssen die ersten 3 übersprungen und die nächsten 3 genommen werden:
int pageSize = 3;
int pageNumber = 2; // Zweite Seite
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. Kombinieren von distinct, limit, skip
Diese Methoden kann und sollte man je nach Aufgabe kombinieren.
Beispiel: 2 eindeutige Namen ab dem dritten in der Reihenfolge erhalten
List<String> result = names.stream()
.distinct() // Duplikate entfernen: [Alice, Bob, Eva, Denis, Gleb]
.skip(2) // Alice und Bob überspringen: [Eva, Denis, Gleb]
.limit(2) // Nur zwei nehmen: [Eva, Denis]
.collect(Collectors.toList());
System.out.println(result);
// Ausgabe: [Eva, Denis]
Beispiel mit Filterung
Angenommen, wir sollen die ersten 2 eindeutigen E‑Mail‑Adressen erhalten, die den Buchstaben „a“ enthalten:
List<String> emails = users.stream()
.map(user -> user.email)
.filter(email -> email.contains("a"))
.distinct()
.limit(2)
.collect(Collectors.toList());
System.out.println(emails);
// Ausgabe: [alice@mail.com, eva@mail.com]
5. Praxis: Aufgaben zur Anwendung
Aufgabe 1. Eine Liste eindeutiger Benutzernamen erhalten, deren Länge größer als 3 Zeichen ist
List<String> longUniqueNames = names.stream()
.filter(name -> name.length() > 3)
.distinct()
.collect(Collectors.toList());
System.out.println(longUniqueNames);
// Zum Beispiel: [Alice, Denis]
Aufgabe 2. Die 3. und 4. eindeutige E‑Mail‑Adresse aus der Benutzerliste ermitteln
List<String> thirdAndFourthEmail = users.stream()
.map(user -> user.email)
.distinct()
.skip(2)
.limit(2)
.collect(Collectors.toList());
System.out.println(thirdAndFourthEmail);
// Zum Beispiel: [eva@mail.com, gleb@mail.com]
Aufgabe 3. Die ersten 5 eindeutigen Zahlen erhalten, die größer als 10 sind
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);
// Ausgabe: [12, 17, 23, 42, 19]
6. Visuelles Schema: Reihenfolge der Operationen
graph TD
A[Ausgangsliste] --> B[filter]
B --> C[distinct]
C --> D[skip]
D --> E[limit]
E --> F[collect]
Kommentar:
Üblicherweise wendet man zuerst filter an, entfernt dann Duplikate mit distinct, verwendet danach skip und limit und am Ende – collect. Die Reihenfolge kann jedoch je nach Aufgabe variieren.
7. Typische Fehler bei der Arbeit mit distinct, limit, skip
Fehler Nr. 1: Erwartung, dass distinct Duplikate nach „beliebigen“ Kriterien entfernt.
Tatsächlich arbeitet distinct über die equals-Methode des Objekts. Wenn Sie Eindeutigkeit nach einem bestimmten Feld möchten (z. B. nur nach email) und nicht nach dem gesamten Objekt, müssen Sie entweder equals/hashCode überschreiben oder Tricks mit Collectors.toMap() oder zusätzlicher Filterung verwenden.
Fehler Nr. 2: Falsche Reihenfolge der Operationen.
Wenn man zuerst limit anwendet und dann distinct, können Duplikate übrig bleiben — denn Sie haben den Stream auf die ersten N Elemente begrenzt, und darunter können gleiche sein.
Fehler Nr. 3: Mit skip mehr Elemente überspringen, als vorhanden sind.
Wenn Sie versuchen, mehr Elemente zu überspringen, als der Stream enthält, ist das Ergebnis einfach eine leere Liste — es gibt keinen Fehler, aber das Ergebnis kann überraschen.
Fehler Nr. 4: Nicht berücksichtigte Performance.
Die Methoden distinct, limit, skip können bei sehr großen Streams ineffizient sein, insbesondere wenn der Stream ungeordnet ist oder die Elemente komplex sind. In 99 % der Alltagsfälle ist das kein Problem, aber wenn Sie mit Millionen von Datensätzen arbeiten, sollten Sie darüber nachdenken.
Fehler Nr. 5: Vergessenes Überschreiben von equals/hashCode für eigene Klassen.
Wenn Sie mit benutzerdefinierten Objekten arbeiten (z. B. User), betrachtet distinct die Objekte ohne Überschreiben dieser Methoden als verschieden, selbst wenn sie „logisch“ identisch sind.
GO TO FULL VERSION