CodeGym /Cursos /JAVA 25 SELF /Zip (zip), geração de streams (iterate, generate)

Zip (zip), geração de streams (iterate, generate)

JAVA 25 SELF
Nível 32 , Lição 3
Disponível

1. Introdução ao zip

Em programação, o termo zip (ou “emparelhamento”, “costura”) refere-se a uma operação que pega duas (ou mais) listas e as combina em um único stream de pares: um elemento de cada lista. Se você conhece Python, lá existe a função zip, que faz exatamente isso.

Exemplo:

  • Há uma lista de nomes: ["Anya", "Boris", "Vika"]
  • Há uma lista de idades: [20, 25, 19]
  • Depois do “zip”, teremos: [("Anya", 20), ("Boris", 25), ("Vika", 19)]

Isso é útil quando precisamos processar duas coleções em sincronia — por exemplo, criar objetos nos quais nome e idade andam juntos.

Por que não há zip no Stream API?
No Stream API padrão (até Java 22) não existe o método zip. O motivo é que streams podem ser infinitos, coleções podem ter comprimentos diferentes e nem sempre é óbvio como se comportar quando uma coleção é maior que a outra. Mas, na prática, zip é necessário com frequência.

2. Implementando zip em Java: como viver sem o método embutido

A maneira mais simples — por índices

Se você tiver duas listas e souber que são coleções “comuns” (por exemplo, List<A>, List<B>), pode usar índices:

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

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

Saída:

Anya (20)
Boris (25)
Vika (19)

O que acontece?

  • Pegamos o tamanho mínimo entre as duas listas — para não sair dos limites.
  • Usamos IntStream.range(0, size) — criamos um stream de índices.
  • Para cada índice, pegamos um elemento de cada lista e “costuramos” ambos.
  • Coletamos o resultado em uma lista com Collectors.toList().

É possível fazer zip para Stream<T>?

Tecnicamente — sim, mas só é conveniente quando ambos os streams são finitos e há uma estrutura com acesso rápido por índice por trás (ou seja, na prática, são List). Para streams “de verdade” (por exemplo, infinitos) uma implementação correta de zip é mais complexa e exige lógica adicional.

Alternativas: bibliotecas de terceiros

Se você quer um zip “pronto”, pode usar bibliotecas:

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

Neste curso seguimos a biblioteca padrão, portanto veremos maneiras “manuais”.

3. Exemplos práticos de uso de zip

Exemplo 1. Percorrer duas coleções em sincronia (somando elementos)

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]

Exemplo 2. Zip de strings e caracteres

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

Visualização (esquema)

names:   [Anya] [Boris] [Vika]
ages:    [20 ] [25   ] [19  ]
          |      |      |
zip ---> (Anya,20) (Boris,25) (Vika,19)

4. Stream.iterate e Stream.generate — geração de novos streams

Às vezes, não precisamos apenas processar coleções existentes, mas também criar novas sequências “na hora”. Para isso, o Stream API tem dois métodos úteis:

  • Stream.iterate — cria uma sequência por uma regra (por exemplo, progressão aritmética).
  • Stream.generate — cria um stream em que cada elemento é calculado por um Supplier (por exemplo, número aleatório, hora atual etc.).

Stream.iterate

Sintaxe:

Stream.iterate(seed, unaryOperator)
  • seed — valor inicial;
  • unaryOperator — função que calcula o próximo elemento.

Exemplo 1: Progressão aritmética

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

Exemplo 2: Geração de datas

import java.time.LocalDate;

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

Exemplo 3: Stream infinito — não se esqueça de limit!

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

No Java 9+ surgiu uma variação sobrecarregada com predicado-condição:

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

Stream.generate

Sintaxe:

Stream.generate(Supplier<T>)

Cada elemento é calculado chamando Supplier.get().

Exemplo 1: Números aleatórios

import java.util.Random;

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

Exemplo 2: Geração de valores iguais

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

Exemplo 3: Identificadores únicos

import java.util.UUID;

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

Visualização (esquema)

Stream.iterate:

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

Stream.generate:

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

5. Exemplos de uso: geração de dados para o aplicativo

Suponha que temos a classe Student:

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

Exemplo 1. Geração de alunos de teste

List<String> names = Arrays.asList("Anya", "Boris", "Vika", "Gleb", "Dasha");
Stream<Student> students = IntStream.range(0, names.size())
    .mapToObj(i -> new Student(names.get(i), 18 + i));

students.forEach(System.out::println);
// Anya (18), Boris (19), Vika (20), Gleb (21), Dasha (22)

Exemplo 2. Geração de alunos aleatórios

Random random = new Random();
List<String> pool = Arrays.asList("Ira", "Oleg", "Maksim", "Tanya", "Sergey");

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

randomStudents.limit(3).forEach(System.out::println);
// Por exemplo: Tanya (19), Oleg (21), Ira (20)

Exemplo 3. Geração de uma sequência de datas para um relatório

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

Comparação: quando usar zip, iterate, generate

  • zip — quando você precisa processar duas (ou mais) listas/streams em sincronia, unindo elementos por índice.
  • iterate — quando você precisa de uma sequência baseada em uma regra (números, datas, passos).
  • generate — quando cada elemento é calculado de forma independente (valores aleatórios, IDs únicos).

7. Erros comuns ao trabalhar com zip e geração de streams

Erro nº 1: Stream não limitado sem limit. Se você usar Stream.iterate ou Stream.generate sem limitar com limit, o programa pode travar ou consumir toda a memória.

Stream.generate(() -> 1).forEach(System.out::println); // Nunca vai terminar!

Erro nº 2: Tratamento incorreto de comprimentos diferentes no zip. Se uma lista for maior que a outra, percorra até o comprimento mínimo; caso contrário, você terá IndexOutOfBoundsException.

Erro nº 3: Tentar fazer zip de Streams<T> comuns. Streams comuns não têm acesso por índice. Um zip prático é feito mais frequentemente para List.

Erro nº 4: Modificar a coleção enquanto gera o stream. Se você mudar a coleção enquanto há um stream sobre ela, pode obter ConcurrentModificationException. Gere novos dados — não mude os antigos “na hora”.

Erro nº 5: Perda de ordem. Se a ordem for importante (por exemplo, com zip), use List, não Set — caso contrário, a ordem dos elementos será imprevisível.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION