CodeGym /Cursos /JAVA 25 SELF /Análise de erros comuns ao trabalhar com streams

Análise de erros comuns ao trabalhar com streams

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

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);
1
Pesquisa/teste
Uniões e projeções, nível 32, lição 4
Indisponível
Uniões e projeções
Stream API: uniões e projeções
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION