CodeGym /Cursos /JAVA 25 SELF /Transformação de coleções com Stream

Transformação de coleções com Stream

JAVA 25 SELF
Nível 30 , Lição 4
Disponível

1. Converter List → Set e vice-versa

Vamos relembrar por que precisamos transformar coleções. Com frequência, em tarefas reais precisamos:

  • Obter da lista apenas elementos únicos (por exemplo, lista de e-mails → conjunto de endereços únicos).
  • Construir um mapeamento (Map), por exemplo, a partir de uma lista de nomes obter o mapa “nome → comprimento do nome”.
  • Juntar elementos em uma única string (por exemplo, para uma saída mais bonita).

Antes, era preciso escrever muito código com laços, condições e coleções temporárias. Com o Stream API tudo ficou mais simples e… mais elegante!

Exemplo: obter um conjunto de nomes únicos a partir de uma lista

Suponha que temos uma lista de nomes (às vezes alguém se cadastra duas vezes no seu programa — acontece!):

List<String> names = List.of("Anna", "Sergey", "Anna", "Maria", "Ivan", "Sergey");

Nossa tarefa — obter uma coleção em que cada nome apareça apenas uma vez, ou seja, um conjunto (Set). Com o Stream API isso se faz literalmente em uma linha:

Set<String> uniqueNames = names.stream()
    .collect(Collectors.toSet());
System.out.println(uniqueNames);

Saída:

[Maria, Ivan, Anna, Sergey]

(A ordem em Set não é garantida — não estranhe se a sua for diferente.)

E se for o contrário: Set → List?

Às vezes é o contrário — transformar um conjunto em uma lista (por exemplo, para ordenar ou acessar por índice):

List<String> namesList = uniqueNames.stream()
    .collect(Collectors.toList());
System.out.println(namesList);

2. Transformação em Map: Collectors.toMap()

Exemplo: a partir de uma lista de nomes obter um Map “nome → comprimento do nome”

Às vezes dá vontade de ser não apenas programador, mas um verdadeiro cartógrafo — construir mapas! Vamos tentar:

List<String> names = List.of("Anna", "Sergey", "Maria", "Ivan");

Map<String, Integer> nameToLength = names.stream()
    .collect(Collectors.toMap(
        name -> name,         // chave — o próprio nome
        name -> name.length() // valor — comprimento do nome
    ));

System.out.println(nameToLength);

Saída:

{Maria=5, Ivan=4, Anna=4, Sergey=6}

Ponto importante: chaves duplicadas

Se na lista original houver nomes iguais, ao tentar coletá-los em um Map ocorrerá um erro IllegalStateException: Duplicate key. O Java não gosta quando você tenta colocar dois valores com a mesma chave.

Como tratar duplicatas?
Você pode indicar o que fazer em caso de colisão de chaves — por exemplo, manter o primeiro valor ou o último:

List<String> names = List.of("Anna", "Sergey", "Anna", "Maria", "Ivan", "Sergey");

Map<String, Integer> nameToLength = names.stream()
    .collect(Collectors.toMap(
        name -> name,
        name -> name.length(),
        (oldValue, newValue) -> oldValue // manter o primeiro valor
    ));

System.out.println(nameToLength);

Agora o programa não vai falhar, e no Map entrará apenas a primeira ocorrência de cada nome.

Exemplo: Map com objetos

Vamos complicar um pouco: temos uma lista de usuários e queremos construir um Map “nome → usuário”:

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

// Exemplo de lista de usuários
List<User> users = List.of(
    new User("Anna", 25),
    new User("Sergey", 30),
    new User("Maria", 22)
);

Map<String, User> nameToUser = users.stream()
    .collect(Collectors.toMap(
        user -> user.name,
        user -> user
    ));

System.out.println(nameToUser);

Saída:

{Maria=Maria (22), Anna=Anna (25), Sergey=Sergey (30)}

3. Juntar em uma string: Collectors.joining()

Às vezes, em vez de apenas coletar, queremos criar uma string bonita para mostrar ao usuário ou no log. Por exemplo, juntar todos os nomes separados por vírgula:

List<String> names = List.of("Anna", "Sergey", "Maria", "Ivan");

String result = names.stream()
    .collect(Collectors.joining(", "));

System.out.println(result);

Saída:

Anna, Sergey, Maria, Ivan

É possível adicionar prefixo e sufixo

String result = names.stream()
    .collect(Collectors.joining(", ", "Lista: [", "]"));

System.out.println(result);

Saída:

Lista: [Anna, Sergey, Maria, Ivan]

4. Operações terminais: forEach, collect, count, anyMatch, allMatch, noneMatch

Método forEach

Já conhecemos bem o forEach: esta operação executa uma ação para cada elemento do stream.

names.stream().forEach(name -> System.out.println("Olá, " + name + "!"));

Método collect

Coleta os elementos em uma coleção, string ou outra estrutura. A operação mais comum é coletar em List ou Set com Collectors.toList() e Collectors.toSet().

Método count

Conta a quantidade de elementos no stream.

long count = names.stream()
    .filter(name -> name.length() > 4)
    .count();
System.out.println("Nomes com mais de 4 letras: " + count);

Métodos anyMatch, allMatch, noneMatch

Verificam se a condição se cumpre para pelo menos um elemento (anyMatch), para todos (allMatch) ou para nenhum (noneMatch).

boolean hasShortName = names.stream()
    .anyMatch(name -> name.length() < 4);
System.out.println("Há um nome curto? " + hasShortName);

boolean allLong = names.stream()
    .allMatch(name -> name.length() > 3);
System.out.println("Todos os nomes têm mais de 3 letras? " + allLong);

boolean noneIvan = names.stream()
    .noneMatch(name -> name.equals("Ivan"));
System.out.println("Não há Ivan? " + noneIvan);

Saída:

Há um nome curto? false
Todos os nomes têm mais de 3 letras? true
Não há Ivan? false

5. Operações terminais e intermediárias: reforçando os conceitos

Operações intermediárias (filter, map, distinct, sorted, limit, skip, peek) — retornam um novo Stream e permitem construir cadeias.

Operações terminais (forEach, collect, count, anyMatch, allMatch, noneMatch, reduce, findFirst, findAny) — encerram o stream; depois delas não haverá mais resultado!

Exemplo de pipeline:

List<String> result = users.stream()
    .filter(user -> user.age > 20)
    .map(user -> user.name.toUpperCase())
    .distinct()
    .sorted()
    .collect(Collectors.toList());

System.out.println(result);

Saída:

[ANNA, IVAN, MARIA, SERGEY]

6. Erros comuns ao transformar coleções com Stream

Erro nº 1: não tratar chaves duplicadas em toMap
Se na coleção de origem houver chaves duplicadas e você usar Collectors.toMap() sem um tratador explícito, o programa lançará uma exceção. Para esses casos, sempre informe a função de mesclagem:

// Manter o último valor
.toMap(keyMapper, valueMapper, (oldVal, newVal) -> newVal)

Erro nº 2: usar forEach em vez de collect
Às vezes iniciantes tentam “montar” uma coleção com forEach, por exemplo:

List<String> list = new ArrayList<>();
names.stream().forEach(name -> list.add(name)); // Funciona, mas não é o jeito do Stream!

Prefira usar collect(Collectors.toList()) — é mais seguro e mais limpo.

Erro nº 3: tentar reutilizar o stream
Um stream só pode ser usado uma vez. Após uma operação terminal (por exemplo, collect, forEach), tentar continuar trabalhando com o mesmo Stream levará a IllegalStateException.

Erro nº 4: quebrar o princípio “sem efeitos colaterais”
As operações intermediárias devem ser “puras” (sem modificar variáveis externas). Evite alterar algo fora do stream dentro de map ou filter.

Erro nº 5: ignorar a ordem em Set e Map
Se a ordem dos elementos for importante, use coleções apropriadas — por exemplo, LinkedHashSet, TreeMap — e indique o coletor adequado.

1
Pesquisa/teste
Fundamentos do Stream API, nível 30, lição 4
Indisponível
Fundamentos do Stream API
Fundamentos do Stream API
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION