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.
GO TO FULL VERSION