CodeGym /Courses /JAVA 25 SELF /Zipping (zip), stream generation (iterate, generate)

Zipping (zip), stream generation (iterate, generate)

JAVA 25 SELF
Level 32, Lesson 3
Available

1. Introduction to zip

In programming, the term zip (or “zipping”, “stitching”) refers to an operation that takes two (or more) lists and combines them into one stream of pairs: one element from each list at a time. If you are familiar with Python, it has a zip function that does exactly this.

Example:

  • There is a list of names: ["Alice", "Bob", "Victoria"]
  • There is a list of ages: [20, 25, 19]
  • After zipping you get: [("Alice", 20), ("Bob", 25), ("Victoria", 19)]

This is handy when you need to process two collections in sync—for example, to create objects where name and age go together.

Why isn’t there zip in the Stream API?
The standard Stream API (up to Java 22) has no zip method. The reason is that streams can be infinite, collections can have different lengths, and it’s not always obvious how to behave if one collection is longer than the other. But in practice, zip is often needed.

2. Implementing zip in Java: how to live without a built-in method

The simplest way—via indexes

If you have two lists and you know for sure they are “regular” collections (for example, List<A>, List<B>), you can use indexes:

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

public class ZipExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Victoria");
        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 + ")"; }
    }
}

Output:

Alice (20)
Bob (25)
Victoria (19)

What’s happening?

  • We take the minimal size of the two lists so we don’t go out of bounds.
  • We use IntStream.range(0, size) to create a stream of indexes.
  • For each index, we take one element from each list and stitch them together.
  • We collect the result into a list via Collectors.toList().

Can we implement zip for Stream<T>?

Technically—yes, but it’s convenient only when both streams are finite and backed by a structure with fast random access (that is, effectively the same List). For “real” streams (e.g., infinite ones), a correct zip implementation is more complex and requires additional logic.

Alternative options: third‑party libraries

If you want a “ready-made” zip, you can use libraries:

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

In this course we stick to the standard library, so we consider “manual” approaches.

3. Practical examples of using zip

Example 1. Synchronous traversal of two collections (summing elements)

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]

Example 2. Zipping strings and characters

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.]

Visualisation (diagram)

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

4. Stream.iterate and Stream.generate—generating new streams

Sometimes we not only need to process existing collections, but also create new sequences on the fly. For this, the Stream API has two useful methods:

  • Stream.iterate—creates a sequence by a rule (for example, an arithmetic progression).
  • Stream.generate—creates a stream where each element is computed via a Supplier (for example, a random number, the current time, etc.).

Stream.iterate

Syntax:

Stream.iterate(seed, unaryOperator)
  • seed—the starting value;
  • unaryOperator—a function that computes the next element.

Example 1: Arithmetic progression

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

Example 2: Generating dates

import java.time.LocalDate;

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

Example 3: Infinite stream—don’t forget limit!

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

In Java 9+ an overloaded variant with a predicate condition appeared:

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>)

Each element is computed by calling Supplier.get().

Example 1: Random numbers

import java.util.Random;

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

Example 2: Generating identical values

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

Example 3: Unique identifiers

import java.util.UUID;

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

Visualisation (diagram)

Stream.iterate:

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

Stream.generate:

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

5. Usage examples: generating app data

Suppose we have a Student class:

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

Example 1. Generating test students

List<String> names = Arrays.asList("Alice", "Bob", "Victoria", "George", "Daisy");
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), Victoria (20), George (21), Daisy (22)

Example 2. Generating random students

Random random = new Random();
List<String> pool = Arrays.asList("Irene", "Oliver", "Max", "Tina", "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);
// For example: Tina (19), Oliver (21), Irene (20)

Example 3. Generating a sequence of dates for a report

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

Comparison: when to use zip, iterate, generate

  • zip—when you need to synchronously process two (or more) lists/streams by joining elements by index.
  • iterate—when you need a sequence by a given rule (numbers, dates, steps).
  • generate—when each element is computed independently (random values, unique IDs).

6. Common mistakes when working with zip and stream generation

Error No. 1: Unbounded stream without limit. If you use Stream.iterate or Stream.generate without limiting via limit, the program may hang or eat all the memory.

Stream.generate(() -> 1).forEach(System.out::println); // Will never finish!

Error No. 2: Improper handling of different lengths when zipping. If one list is longer than the other, iterate up to the minimal length, otherwise you’ll get IndexOutOfBoundsException.

Error No. 3: Attempting to zip ordinary Stream<T>. Plain streams do not have random access by index. A practical zip is usually done for List.

Error No. 4: Modifying a collection while generating a stream. If you change a collection while a stream is iterating over it, you can get a ConcurrentModificationException. Generate new data—do not mutate the old ones on the fly.

Error No. 5: Losing order. If order matters (for example, with zip), use a List, not a Set—otherwise the element order will be unpredictable.

1
Task
JAVA 25 SELF, level 32, lesson 3
Locked
Creating Labels for Items with Limited Codes 🏷️
Creating Labels for Items with Limited Codes 🏷️
1
Task
JAVA 25 SELF, level 32, lesson 3
Locked
Generating test profiles for a social network 👥
Generating test profiles for a social network 👥
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION