1. Erro: reutilização do stream
Um stream (Stream) em Java é uma ferramenta de uso único. Assim que você executa uma operação terminal (por exemplo, collect(), forEach(), count()), o stream é fechado e se torna impróprio para uso posterior.
Exemplo de erro
Stream<String> stream = Stream.of("a", "b", "c");
stream.forEach(System.out::println); // Tudo certo
stream.forEach(System.out::println); // Vai lançar IllegalStateException!
Saída:
a
b
c
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
Como evitar
Se precisar executar várias operações, crie um novo stream a cada vez:
List<String> list = List.of("a", "b", "c");
list.stream().forEach(System.out::println); // OK
list.stream().count(); // OK
Dica: Um stream é como um copo descartável do refeitório: usou — jogue fora; pegue um novo para a próxima bebida.
2. Erro: modificar a coleção durante o stream
Se você modificar a coleção de origem enquanto a percorre com um stream, receberá ConcurrentModificationException. Isso é uma dor comum ao filtrar e remover elementos.
Exemplo de erro
List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));
names.stream().forEach(name -> {
if (name.startsWith("A")) {
names.remove(name); // BUM!
}
});
Resultado:
Exception in thread "main" java.util.ConcurrentModificationException
Como fazer certo
Use métodos que não modifiquem a coleção durante a iteração ou colete o resultado em uma nova coleção:
names.removeIf(name -> name.startsWith("A")); // OK, removeIf é seguro
// Ou com a Stream API:
List<String> filtered = names.stream()
.filter(name -> !name.startsWith("A"))
.collect(Collectors.toList());
Lembre-se: Os streams gostam de pureza — não toque na coleção de origem enquanto eles a estiverem consumindo.
3. Erro: esquecer limit em streams infinitos
Os métodos Stream.iterate e Stream.generate podem criar streams infinitos. Se você esquecer de limitar seu tamanho com limit, pode travar para sempre (ou até um OutOfMemoryError).
Exemplo de erro
Stream<Integer> infinite = Stream.iterate(0, n -> n + 1);
infinite.forEach(System.out::println); // O programa nunca terminará!
Como fazer certo
Limite o stream com limit:
Stream<Integer> finite = Stream.iterate(0, n -> n + 1).limit(10);
finite.forEach(System.out::println); // Imprime os números de 0 a 9
Dica: Streams infinitos lembram uma série sem episódio final: alguém precisa encerrar — e é melhor que seja você!
4. Erro: uso incorreto de null
A API Stream não gosta de null. Se aparecer um null na coleção, muitas operações podem lançar NullPointerException, especialmente se você chamar métodos nos elementos.
Exemplo de erro
List<String> names = Arrays.asList("Alice", null, "Bob");
names.stream()
.map(String::toUpperCase) // BUM! Em null
.forEach(System.out::println);
Resultado:
Exception in thread "main" java.lang.NullPointerException
Como fazer certo
Filtre valores nulos antecipadamente:
names.stream()
.filter(Objects::nonNull)
.map(String::toUpperCase)
.forEach(System.out::println);
Trabalhando com Optional
Se você tem Stream<Optional<T>>, use flatMap(Optional::stream) (Java 9+) para “desembrulhar”:
List<Optional<String>> optionals = List.of(Optional.of("A"), Optional.empty(), Optional.of("B"));
optionals.stream()
.flatMap(Optional::stream)
.forEach(System.out::println); // Imprime apenas "A" e "B"
Dica: Evite adicionar null às coleções. Se não houver alternativa, filtre-os no começo.
5. Erro: perda da ordem dos elementos
Algumas operações (por exemplo, flatMap, concat) podem afetar a ordem dos elementos, especialmente se você trabalhar com streams paralelos ou unir coleções diferentes.
Exemplo de erro
List<Integer> list1 = List.of(1, 2, 3);
List<Integer> list2 = List.of(4, 5, 6);
Stream<Integer> combined = Stream.concat(list1.stream(), list2.stream());
combined.forEach(System.out::print); // 123456 — a ordem é preservada
// Mas se usar streams paralelos:
Stream<Integer> par = Stream.concat(list1.parallelStream(), list2.parallelStream());
par.forEach(System.out::print); // A ordem pode ser inesperada!
Como fazer certo
Se a ordem é importante, evite streams paralelos ou use métodos que garantem a ordem (forEachOrdered):
par.forEachOrdered(System.out::print);
Fato: A API Stream preserva a ordem por padrão, a menos que você mesmo a “quebre”.
6. Erro: união ineficiente de coleções
Para operações com conjuntos, prefira Set a List, para evitar duplicatas e melhorar o desempenho. Também é ineficiente escrever loops aninhados onde se pode usar flatMap.
Exemplo de erro
List<String> list1 = List.of("a", "b", "c");
List<String> list2 = List.of("b", "c", "d");
// Ineficiente:
List<String> union = new ArrayList<>(list1);
for (String s : list2) {
if (!union.contains(s)) { // contains em List — O(n)
union.add(s);
}
}
// Eficiente com Set e Stream API:
Set<String> unionSet = Stream.concat(list1.stream(), list2.stream())
.collect(Collectors.toSet());
Ou para diferença:
// Ineficiente:
List<String> diff = new ArrayList<>(list1);
diff.removeAll(list2); // OK, mas se fosse via stream e list.contains — seria lento
// Eficiente:
Set<String> set2 = new HashSet<>(list2);
List<String> difference = list1.stream()
.filter(s -> !set2.contains(s))
.collect(Collectors.toList());
Dica: Para trabalhar com elementos únicos, use sempre Set. Para coleções grandes, evite loops aninhados; prefira stream + flatMap.
7. Erro: reutilizar operações terminais
Às vezes dá vontade de “dividir” o stream em dois ramos, por exemplo, coletar elementos em uma lista e, em paralelo, contar a quantidade. Mas o stream é “de uso único”, e a segunda operação causará erro.
Exemplo de erro
Stream<String> stream = Stream.of("a", "b", "c");
List<String> list = stream.collect(Collectors.toList());
long count = stream.count(); // BUM! IllegalStateException
Como fazer certo
Se precisar usar o stream mais de uma vez, primeiro colete-o em uma coleção:
List<String> list = Stream.of("a", "b", "c").collect(Collectors.toList());
long count = list.size();
Dica: Se quiser fazer as duas coisas, primeiro colete em uma coleção.
8. Erro: esquecer close() em recursos
Se você usa streams associados a recursos (por exemplo, Files.lines(Path)), não se esqueça de fechá-los após o uso para evitar vazamentos.
Exemplo de erro
Stream<String> lines = Files.lines(Path.of("file.txt"));
lines.forEach(System.out::println); // OK, mas o recurso não foi fechado!
Como fazer certo
Use try-with-resources:
try (Stream<String> lines = Files.lines(Path.of("file.txt"))) {
lines.forEach(System.out::println);
}
Dica: Streams com arquivos são como chaleira: ferveu — desligue!
9. Erro: tratamento incorreto de Optional e null em flatMap
Ao trabalhar com coleções do tipo List<Optional<T>>, muitos usam map(Optional::get), o que leva a exceções se o Optional estiver vazio.
Exemplo de erro
List<Optional<String>> optionals = List.of(Optional.of("A"), Optional.empty());
optionals.stream()
.map(Optional::get) // BUM! NoSuchElementException
.forEach(System.out::println);
Como fazer certo
Use flatMap(Optional::stream) (Java 9+):
optionals.stream()
.flatMap(Optional::stream)
.forEach(System.out::println); // Apenas "A"
Dica: Optional não é apenas um invólucro, mas uma ferramenta para trabalhar com ausência de valor de forma segura.
10. Erro: equals/hashCode implementados de forma incorreta
Operações com conjuntos (Set, distinct, união, interseção) dependem da implementação correta dos métodos equals e hashCode.
Exemplo de erro
class Student {
String name;
int age;
// Sem equals e hashCode!
}
Set<Student> students = new HashSet<>();
students.add(new Student("Alice", 20));
students.add(new Student("Alice", 20));
System.out.println(students.size()); // 2, embora devessem ser iguais!
Como fazer certo
Substitua equals e hashCode nas suas classes se planeja usá-las em coleções Set, Map ou em operações distinct.
11. Erro: usar o stream após collect
Após uma operação terminal (por exemplo, collect), o stream é fechado. Qualquer tentativa de continuar usando o mesmo stream resultará em erro.
Exemplo de erro
Stream<String> stream = Stream.of("a", "b", "c");
List<String> list = stream.collect(Collectors.toList());
stream.filter(s -> s.startsWith("a")).forEach(System.out::println); // Exception!
Como fazer certo
Crie um novo stream a partir da coleção:
list.stream().filter(s -> s.startsWith("a")).forEach(System.out::println);
12. Erro: uso incorreto de parallelStream
Muitos pensam que parallelStream() sempre acelera o processamento. Na verdade, para coleções pequenas ou operações simples, o paralelismo pode apenas deixar mais lento por causa do overhead de gerenciamento de threads.
Exemplo de erro
List<Integer> numbers = IntStream.range(0, 10).boxed().collect(Collectors.toList());
numbers.parallelStream().forEach(System.out::println); // Pode ser mais lento do que um stream() normal
Como fazer certo
Use parallelStream apenas para coleções realmente grandes ou para computações pesadas, especialmente se as operações forem puras (sem efeitos colaterais) e associativas.
13. Erro: operador terminal esquecido
Operações intermediárias (map, filter, flatMap etc.) não são executadas até que um operador terminal seja chamado (collect, forEach, count etc.).
Exemplo de erro
Stream<String> stream = Stream.of("a", "b", "c").map(String::toUpperCase);
// Nada vai acontecer!
Como fazer certo
Lembre-se de chamar um operador terminal:
stream.forEach(System.out::println);
GO TO FULL VERSION