CodeGym /Kurse /JAVA 25 SELF /Teiloperationen der Stream‑API: distinct, limit, skip

Teiloperationen der Stream‑API: distinct, limit, skip

JAVA 25 SELF
Level 30 , Lektion 2
Verfügbar

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.

1
Aufgabe
JAVA 25 SELF, Level 30, Lektion 2
Gesperrt
Verwaltung der Warteschlange für eine exklusive Party 🎉
Verwaltung der Warteschlange für eine exklusive Party 🎉
1
Aufgabe
JAVA 25 SELF, Level 30, Lektion 2
Gesperrt
Erstellung der Top-4 eindeutigen Verkaufskennzahlen 💰
Erstellung der Top-4 eindeutigen Verkaufskennzahlen 💰
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION