JavaRush /Курсы /JAVA 25 SELF /Зипование (zip), генерация потоков (iterate, generate)

Зипование (zip), генерация потоков (iterate, generate)

JAVA 25 SELF
32 уровень , 3 лекция
Открыта

1. Знакомство с zip

В программировании под термином zip (или «зипование», «сшивка») понимается операция, которая берёт два (или более) списка и объединяет их в один поток пар: по одному элементу из каждого списка. Если вы знакомы с Python, там есть функция zip, которая делает именно это.

Пример:

  • Есть список имён: ["Аня", "Борис", "Вика"]
  • Есть список возрастов: [20, 25, 19]
  • После «зипования» получится: [("Аня", 20), ("Борис", 25), ("Вика", 19)]

Это удобно, когда нужно синхронно обрабатывать две коллекции — например, создавать объекты, в которых имя и возраст идут вместе.

Почему в Stream API нет zip?
В стандартном Stream API (до Java 22) метода zip нет. Причина в том, что стримы могут быть бесконечными, коллекции — разной длины, и не всегда очевидно, как вести себя, если одна коллекция длиннее другой. Но на практике zip нужен часто.

2. Реализация zip на Java: как жить без встроенного метода

Самый простой способ — через индексы

Если у вас есть два списка, и вы точно знаете, что это «обычные» коллекции (например, List<A>, List<B>), можно воспользоваться индексами:

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

public class ZipExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Аня", "Борис", "Вика");
        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 + ")"; }
    }
}

Вывод:

Аня (20)
Борис (25)
Вика (19)

Что происходит?

  • Берём минимальный размер из двух списков — чтобы не выйти за границы.
  • Используем IntStream.range(0, size) — создаём поток индексов.
  • Для каждого индекса берём по одному элементу из каждого списка и «сшиваем» их.
  • Собираем результат в список через Collectors.toList().

Можно ли сделать zip для Stream<T>?

Технически — да, но удобно только тогда, когда оба стрима конечные и за ними стоит структура с быстрым доступом по индексу (то есть фактически это те же List). Для «настоящих» потоков (например, бесконечных) корректная реализация zip сложнее и требует дополнительной логики.

Альтернативные варианты: сторонние библиотеки

Если хочется «готовый» zip, можно использовать библиотеки:

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

В курсе придерживаемся стандартной библиотеки, поэтому рассматриваем «ручные» способы.

3. Практические примеры использования zip

Пример 1. Синхронный обход двух коллекций (суммирование элементов)

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]

Пример 2. Зипование строк и символов

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

Визуализация (схема)

names:   [Аня] [Борис] [Вика]
ages:    [20 ] [25   ] [19  ]
          |      |      |
zip ---> (Аня,20) (Борис,25) (Вика,19)

4. Stream.iterate и Stream.generate — генерация новых потоков

Иногда нам нужно не только обрабатывать уже существующие коллекции, но и создавать новые последовательности «на лету». Для этого в Stream API есть два полезных метода:

  • Stream.iterate — создаёт последовательность по правилу (например, арифметическая прогрессия).
  • Stream.generate — создаёт поток, где каждый элемент вычисляется через Supplier (например, случайное число, текущее время и т. д.).

Stream.iterate

Синтаксис:

Stream.iterate(seed, unaryOperator)
  • seed — стартовое значение;
  • unaryOperator — функция, вычисляющая следующий элемент.

Пример 1: Арифметическая прогрессия

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

Пример 2: Генерация дат

import java.time.LocalDate;

Stream<LocalDate> days = Stream.iterate(LocalDate.now(), date -> date.plusDays(1));
days.limit(3).forEach(System.out::println);
// Например: 2024-06-09, 2024-06-10, 2024-06-11

Пример 3: Бесконечный поток — не забывайте limit!

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

В Java 9+ появился перегруженный вариант с предикатом-условием:

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

Stream.generate

Синтаксис:

Stream.generate(Supplier<T>)

Каждый элемент вычисляется вызовом Supplier.get().

Пример 1: Случайные числа

import java.util.Random;

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

Пример 2: Генерация одинаковых значений

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

Пример 3: Уникальные идентификаторы

import java.util.UUID;

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

Визуализация (схема)

Stream.iterate:

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

Stream.generate:

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

5. Примеры использования: генерация данных для приложения

Пусть у нас есть класс Student:

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

Пример 1. Генерация тестовых студентов

List<String> names = Arrays.asList("Аня", "Борис", "Вика", "Глеб", "Даша");
Stream<Student> students = IntStream.range(0, names.size())
    .mapToObj(i -> new Student(names.get(i), 18 + i));

students.forEach(System.out::println);
// Аня (18), Борис (19), Вика (20), Глеб (21), Даша (22)

Пример 2. Генерация случайных студентов

Random random = new Random();
List<String> pool = Arrays.asList("Ира", "Олег", "Максим", "Таня", "Сергей");

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

randomStudents.limit(3).forEach(System.out::println);
// Например: Таня (19), Олег (21), Ира (20)

Пример 3. Генерация последовательности дат для отчёта

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

Сравнение: когда использовать zip, iterate, generate

  • zip — когда нужно синхронно обработать два (или более) списка/потока, объединяя элементы по индексам.
  • iterate — когда нужна последовательность по заданному правилу (числа, даты, шаги).
  • generate — когда каждый элемент вычисляется независимо (случайные значения, уникальные ID).

6. Типичные ошибки при работе с zip и генерацией потоков

Ошибка №1: Неограниченный поток без limit. Если вы используете Stream.iterate или Stream.generate без ограничения через limit, программа может зависнуть или «съесть» всю память.

Stream.generate(() -> 1).forEach(System.out::println); // Никогда не закончится!

Ошибка №2: Неправильная обработка разных длин при zip. Если один список длиннее другого, идти нужно по минимальной длине, иначе получите IndexOutOfBoundsException.

Ошибка №3: Попытка zip-ить обычные Stream<T>. У обычных стримов нет доступа по индексу. Практичный zip чаще делают для List.

Ошибка №4: Модификация коллекции во время генерации потока. Если менять коллекцию, пока по ней идёт стрим, можно получить ConcurrentModificationException. Генерируйте новые данные — не меняйте старые «на лету».

Ошибка №5: Потеря порядка. Если порядок важен (например, при zip), используйте List, не Set — иначе порядок элементов окажется непредсказуемым.

1
Задача
JAVA 25 SELF, 32 уровень, 3 лекция
Недоступна
Создание бирок для товаров с ограниченными кодами 🏷️
Создание бирок для товаров с ограниченными кодами 🏷️
1
Задача
JAVA 25 SELF, 32 уровень, 3 лекция
Недоступна
Генерация тестовых профилей для социальной сети 👥
Генерация тестовых профилей для социальной сети 👥
Комментарии (8)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Riga Уровень 5
7 мая 2026
в интелидж идея при открытии задачи, какая-то левая папка то ли создается то ли открывается, приходится в ручную файл с задачей перекидывать в другую папку
Gans Electro Уровень 42
31 июля 2026
Нужно перезайти в аккаунт. Вы скорее всего меняли курс и возвращались
Riga Уровень 5
13 сентября 2026
интересно, спасибо
Riga Уровень 5
6 мая 2026
после 5 пункта сразу 7 начинается
Grrbrr7 Уровень 46
30 марта 2026
Забил на вторую задачу после 2х дней)
Vladimir Danilov Уровень 34
18 августа 2026
Она гораздо легче чем кажется!
Victor Уровень 39
25 августа 2026
2 дня - завидное упорство - если перевести в кДж - можно отапливать небольшой город в течение недели.
rwnmei Уровень 46
31 августа 2026
задача реально сложная минут 30 над ней тупил