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

7. Типичные ошибки при работе с 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 лекция
Недоступна
Генерация тестовых профилей для социальной сети 👥
Генерация тестовых профилей для социальной сети 👥
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ