CodeGym /Kurse /JAVA 25 SELF /Zipping (zip), Generierung von Streams (iterate, generate...

Zipping (zip), Generierung von Streams (iterate, generate)

JAVA 25 SELF
Level 32 , Lektion 3
Verfügbar

1. Einführung in zip

In der Programmierung bezeichnet der Begriff zip (auch „Zipping“, „Zusammenführen“) eine Operation, die zwei (oder mehr) Listen nimmt und sie zu einem Stream von Paaren kombiniert: jeweils ein Element aus jeder Liste. Wenn Sie mit Python vertraut sind: Dort gibt es die Funktion zip, die genau das tut.

Beispiel:

  • Es gibt eine Namensliste: ["Alice", "Bob", "Charlie"]
  • Es gibt eine Altersliste: [20, 25, 19]
  • Nach dem „Zipping“ ergibt sich: [("Alice", 20), ("Bob", 25), ("Charlie", 19)]

Das ist praktisch, wenn zwei Collections synchron verarbeitet werden sollen — etwa um Objekte zu erzeugen, in denen Name und Alter zusammengehören.

Warum gibt es im Stream API kein zip?
Im standardmäßigen Stream API (bis Java 22) gibt es keine Methode zip. Der Grund: Streams können unendlich sein, Collections — unterschiedlich lang, und es ist nicht immer klar, wie man sich verhalten soll, wenn eine Collection länger ist als die andere. In der Praxis wird zip jedoch oft gebraucht.

2. Zip in Java implementieren: wie ohne eingebaute Methode leben

Der einfachste Weg — über Indizes

Wenn Sie zwei Listen haben und genau wissen, dass es „gewöhnliche“ Collections sind (zum Beispiel List<A>, List<B>), können Sie Indizes verwenden:

import java.util.*;
import java.util.stream.*;

public class ZipExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
        List<Integer> ages = Arrays.asList(20, 25, 19);

        int size = Math.min(names.size(), ages.size());

        List<Person> people = IntStream.range(0, size)
            .mapToObj(i -> new Person(names.get(i), ages.get(i)))
            .collect(Collectors.toList());

        people.forEach(System.out::println);
    }

    static class Person {
        String name;
        int age;
        Person(String name, int age) { this.name = name; this.age = age; }
        public String toString() { return name + " (" + age + ")"; }
    }
}

Ausgabe:

Alice (20)
Bob (25)
Charlie (19)

Was passiert hier?

  • Wir nehmen die minimale Größe der beiden Listen — um keinen Indexfehler zu bekommen.
  • Wir verwenden IntStream.range(0, size) — wir erzeugen einen Index-Stream.
  • Für jeden Index nehmen wir je ein Element aus jeder Liste und „führen sie zusammen“.
  • Wir sammeln das Ergebnis mit Collectors.toList() in eine Liste.

Kann man zip für Stream<T> machen?

Technisch — ja, aber bequem nur dann, wenn beide Streams endlich sind und dahinter eine Struktur mit schnellem Indexzugriff steht (also faktisch wieder List). Für „echte“ Streams (zum Beispiel unendliche) ist eine korrekte Implementierung von zip komplizierter und benötigt zusätzliche Logik.

Alternative Varianten: externe Bibliotheken

Wenn Sie ein „fertiges“ zip möchten, können Sie Bibliotheken nutzen:

  • org.apache.commons.lang3.Streams.zip (Apache Commons Lang 3.10+)
  • io.vavr.collection.Stream.zip (Vavr)
  • com.codepoetics.protonpack.StreamUtils.zip (ProtonPack)

In diesem Kurs bleiben wir bei der Standardbibliothek, daher betrachten wir „manuelle“ Wege.

3. Praktische Anwendungsbeispiele für zip

Beispiel 1. Synchrones Durchlaufen zweier Collections (Summieren der Elemente)

List<Integer> a = Arrays.asList(1, 2, 3, 4);
List<Integer> b = Arrays.asList(10, 20, 30, 40);

List<Integer> sums = IntStream.range(0, Math.min(a.size(), b.size()))
    .mapToObj(i -> a.get(i) + b.get(i))
    .collect(Collectors.toList());

System.out.println(sums); // [11, 22, 33, 44]

Beispiel 2. Zipping von Strings und Zeichen

String[] words = {"cat", "dog", "fox"};
char[] marks = {'!', '?', '.'};

List<String> zipped = IntStream.range(0, Math.min(words.length, marks.length))
    .mapToObj(i -> words[i] + marks[i])
    .collect(Collectors.toList());

System.out.println(zipped); // [cat!, dog?, fox.]

Visualisierung (Schema)

names:   [Alice] [Bob] [Charlie]
ages:    [20 ] [25   ] [19  ]
          |      |      |
zip ---> (Alice,20) (Bob,25) (Charlie,19)

4. Stream.iterate und Stream.generate — Generierung neuer Streams

Manchmal müssen wir nicht nur bereits existierende Collections verarbeiten, sondern auch neue Sequenzen „on the fly“ erzeugen. Dafür gibt es im Stream API zwei nützliche Methoden:

  • Stream.iterate — erzeugt eine Sequenz nach einer Regel (zum Beispiel eine arithmetische Progression).
  • Stream.generate — erzeugt einen Stream, bei dem jedes Element über einen Supplier berechnet wird (zum Beispiel Zufallszahl, aktuelle Zeit usw.).

Stream.iterate

Syntax:

Stream.iterate(seed, unaryOperator)
  • seed — Startwert;
  • unaryOperator — Funktion, die das nächste Element berechnet.

Beispiel 1: Arithmetische Progression

Stream<Integer> numbers = Stream.iterate(0, n -> n + 2); // 0, 2, 4, 6, ...
numbers.limit(5).forEach(System.out::println);
// Ausgabe: 0 2 4 6 8

Beispiel 2: Generierung von Datumswerten

import java.time.LocalDate;

Stream<LocalDate> days = Stream.iterate(LocalDate.now(), date -> date.plusDays(1));
days.limit(3).forEach(System.out::println);
// Zum Beispiel: 2024-06-09, 2024-06-10, 2024-06-11

Beispiel 3: Unendlicher Stream — vergessen Sie limit nicht!

Stream<Integer> endless = Stream.iterate(1, n -> n * 2);
endless.limit(5).forEach(System.out::println); // 1 2 4 8 16

In Java 9+ kam eine überladene Variante mit Prädikat-Bedingung hinzu:

Stream.iterate(0, n -> n < 10, n -> n + 2)
    .forEach(System.out::println); // 0 2 4 6 8

Stream.generate

Syntax:

Stream.generate(Supplier<T>)

Jedes Element wird durch einen Aufruf von Supplier.get() berechnet.

Beispiel 1: Zufallszahlen

import java.util.Random;

Random random = new Random();
Stream<Integer> randoms = Stream.generate(random::nextInt);
randoms.limit(5).forEach(System.out::println);

Beispiel 2: Generierung identischer Werte

Stream<String> stars = Stream.generate(() -> "*");
stars.limit(4).forEach(System.out::print); // ****

Beispiel 3: Eindeutige Bezeichner

import java.util.UUID;

Stream<String> uuids = Stream.generate(() -> UUID.randomUUID().toString());
uuids.limit(3).forEach(System.out::println);

Visualisierung (Schema)

Stream.iterate:

[seed] -> op() -> op() -> op() -> ...
       n      n+1    n+2    n+3

Stream.generate:

Supplier() -> Supplier() -> Supplier() -> ...
    val1         val2          val3

5. Anwendungsbeispiele: Datengenerierung für eine Anwendung

Angenommen, wir haben die Klasse Student:

class Student {
    String name;
    int age;
    Student(String name, int age) { this.name = name; this.age = age; }
    public String toString() { return name + " (" + age + ")"; }
}

Beispiel 1. Generierung von Test-Studierenden

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Dave", "Eve");
Stream<Student> students = IntStream.range(0, names.size())
    .mapToObj(i -> new Student(names.get(i), 18 + i));

students.forEach(System.out::println);
// Alice (18), Bob (19), Charlie (20), Dave (21), Eve (22)

Beispiel 2. Generierung zufälliger Studierender

Random random = new Random();
List<String> pool = Arrays.asList("Iris", "Oscar", "Max", "Tara", "Sam");

Stream<Student> randomStudents = Stream.generate(() ->
    new Student(
        pool.get(random.nextInt(pool.size())),
        18 + random.nextInt(5)
    )
);

randomStudents.limit(3).forEach(System.out::println);
// Zum Beispiel: Tara (19), Oscar (21), Iris (20)

Beispiel 3. Generierung einer Datumsfolge für einen Bericht

import java.time.LocalDate;

Stream<LocalDate> dates = Stream.iterate(LocalDate.of(2024, 6, 1), d -> d.plusDays(1));
dates.limit(5).forEach(System.out::println);
// 2024-06-01, 2024-06-02, ..., 2024-06-05

Vergleich: Wann verwendet man zip, iterate, generate

  • zip — wenn zwei (oder mehr) Listen/Streams synchron verarbeitet werden sollen, indem Elemente per Index kombiniert werden.
  • iterate — wenn eine Sequenz nach einer Regel benötigt wird (Zahlen, Daten, Schritte).
  • generate — wenn jedes Element unabhängig berechnet wird (Zufallswerte, eindeutige IDs).

7. Typische Fehler bei der Arbeit mit zip und der Generierung von Streams

Fehler Nr. 1: Unbegrenzter Stream ohne limit. Wenn Sie Stream.iterate oder Stream.generate ohne Begrenzung via limit verwenden, kann das Programm hängenbleiben oder den gesamten Speicher verbrauchen.

Stream.generate(() -> 1).forEach(System.out::println); // Wird nie enden!

Fehler Nr. 2: Falscher Umgang mit unterschiedlichen Längen beim zip. Wenn eine Liste länger ist als die andere, gehen Sie über die minimale Länge, sonst erhalten Sie eine IndexOutOfBoundsException.

Fehler Nr. 3: Versuch, normale Stream<T> zu zippen. Normale Streams haben keinen Zugriff per Index. Ein praktikables zip macht man häufiger für List.

Fehler Nr. 4: Modifikation einer Collection während der Stream-Generierung. Wenn Sie eine Collection ändern, während ein Stream darüber läuft, kann eine ConcurrentModificationException auftreten. Erzeugen Sie neue Daten — ändern Sie die alten nicht „on the fly“.

Fehler Nr. 5: Verlust der Reihenfolge. Wenn die Reihenfolge wichtig ist (zum Beispiel bei zip), verwenden Sie List, nicht Set — andernfalls ist die Reihenfolge der Elemente unvorhersehbar.

1
Aufgabe
JAVA 25 SELF, Level 32, Lektion 3
Gesperrt
Erstellen von Etiketten für Artikel mit begrenzten Codes 🏷️
Erstellen von Etiketten für Artikel mit begrenzten Codes 🏷️
1
Aufgabe
JAVA 25 SELF, Level 32, Lektion 3
Gesperrt
Generierung von Testprofilen für ein soziales Netzwerk 👥
Generierung von Testprofilen für ein soziales Netzwerk 👥
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION