CodeGym /Cursos /JAVA 25 SELF /Análise de erros em programação funcional

Análise de erros em programação funcional

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

1. Erros com expressões lambda: captura de variáveis

Em Java, expressões lambda podem usar variáveis do contexto externo. Mas há uma restrição: tais variáveis devem ser final ou “efetivamente finais” (effectively final), ou seja, não podem ser alteradas após a inicialização.

Exemplo de erro

int sum = 0;
List<Integer> list = List.of(1, 2, 3, 4, 5);
list.forEach(n -> sum += n); // Erro de compilação!

Por quê?
O compilador reclama: a variável é usada na lambda, portanto deve ser final ou effectively final, mas sum é modificada dentro da lambda.

Como evitar?

  • Use operações terminais de streams que não exigem variáveis externas: mapToInt + sum().
  • Em casos extremos — contêineres como AtomicInteger ou um array de um único elemento (mas isso é mais um hack).
int sum = list.stream().mapToInt(Integer::intValue).sum();

Analogia
Imagine que a lambda é um “viajante no tempo”: ela “memoriza” o valor da variável no momento da criação e não pode observar como ele muda. A tentativa de modificar — “o paradoxo do avô”; o compilador não permitirá compilar o programa.

2. Erros com escopo e this

Em uma expressão lambda, a palavra-chave this se refere ao objeto externo, e não a uma classe anônima (como seria com classes anônimas).

Exemplo

public class Example {
    int value = 42;

    void foo() {
        Runnable r = () -> {
            System.out.println(this.value); // this — é Example, não Runnable!
        };
        r.run();
    }
}

Importante: ao reescrever código de classes anônimas para lambdas, a lógica de funcionamento de this muda — leve isso em conta para não obter um resultado inesperado.

3. Problemas com estado mutável (efeitos colaterais)

A abordagem funcional recomenda ausência de efeitos colaterais: funções não alteram o estado fora de si mesmas e não mutam coleções/variáveis externas.

List<String> names = new ArrayList<>(List.of("Anna", "Boris", "Vika"));
List<String> newNames = new ArrayList<>();

names.forEach(name -> {
    if (name.startsWith("A")) {
        newNames.add(name); // Efeito colateral!
    }
});

O código “funciona”, mas é menos previsível e perigoso ao usar parallelStream() (risco de condições de corrida e exceções). Fica mais difícil de testar e manter.

Correto: use operações que formem explicitamente um novo resultado sem alterar o estado externo.

List<String> newNames = names.stream()
    .filter(name -> name.startsWith("A"))
    .collect(Collectors.toList());

4. Erros com tipos e generics

Java é uma linguagem de tipagem rígida. Às vezes, o compilador não consegue inferir os tipos a partir de lambdas ou cadeias muito complexas.

Exemplo

List<Object> objects = List.of(1, "texto", 3.14);
List<String> strings = objects.stream()
    .filter(obj -> obj instanceof String)
    .map(obj -> (String) obj)
    .collect(Collectors.toList());

Parece lógico, mas qualquer erro de digitação ou casting incorreto pode levar a um erro de compilação ou, pior, a ClassCastException em tempo de execução.

Como evitar?

  • Adicione tipos explícitos quando a inferência “tropeçar”.
  • Não tenha medo de escrever <String> ou parametrizar lambdas: (String s) -> ....
  • Verifique a compatibilidade de tipos nas conversões.

Caso típico com Optional

Optional<String> opt = Optional.of("hello");
opt.map(s -> s.length()); // resultado — Optional<Integer>

Se você esperava Optional<String>, mas obteve Optional<Integer>, verifique o que sua função retorna.

5. Efeitos colaterais em lambdas e paralelismo

Streams paralelos (parallelStream()) mais efeitos colaterais — combinação perigosa.

Exemplo

List<Integer> numbers = IntStream.range(0, 1000).boxed().collect(Collectors.toList());
List<Integer> results = new ArrayList<>();

numbers.parallelStream().forEach(n -> results.add(n)); // PERIGOSO!

O que pode acontecer?

  • Perda ou duplicação de dados.
  • ConcurrentModificationException ou bugs “misteriosos”.

Como fazer direito?

  • Usar coleções seguras para threads: ConcurrentLinkedQueue, CopyOnWriteArrayList.
  • Melhor ainda — evitar completamente efeitos colaterais e coletar o resultado via collect(...).
List<Integer> results = numbers.parallelStream()
    .map(n -> n)
    .collect(Collectors.toList());

6. Perda de legibilidade: “stream-spaghetti” e cadeias longas

O estilo funcional é bom até que a cadeia vire um “cupom de hipermercado”.

List<String> result = list.stream()
    .filter(s -> s.length() > 2)
    .map(String::trim)
    .map(s -> s.toUpperCase())
    .filter(s -> s.contains("JAVA"))
    .sorted()
    .distinct()
    .collect(Collectors.toList());

Dicas:

  • Quebre cadeias em blocos lógicos.
  • Extraia lambdas complexas para métodos separados com nomes claros.
  • Adicione comentários quando necessário — mesmo em código de Stream.

7. Nomes ruins de variáveis e funções

Nomes excessivamente curtos (x, y, z) dificultam o entendimento.

list.stream()
    .map(x -> x.trim())
    .filter(y -> y.length() > 3)
    .map(z -> z.toUpperCase())
    .forEach(System.out::println);

Use nomes significativos, especialmente se a lambda for multilinha ou expressar lógica não trivial.

8. Erros com null e Optional

A Stream API e as interfaces funcionais não gostam de valores null. Passar null para uma lambda ou stream é uma causa comum de NullPointerException.

List<String> list = Arrays.asList("a", null, "b");
list.stream()
    .map(String::toUpperCase) // Boom! NPE no segundo elemento
    .forEach(System.out::println);

Como fazer certo?

  • Filtre nulls antecipadamente: .filter(Objects::nonNull).
  • Use Optional para representar explicitamente a ausência de valor.

9. Problemas com o tipo de retorno em funções combinadas

Ao usar compose e andThen, é fácil confundir a ordem de aplicação das funções e os tipos esperados.

Function<String, Integer> parse = Integer::parseInt;
Function<Integer, Integer> square = x -> x * x;

Function<String, Integer> parseAndSquare = parse.andThen(square);
// Funciona: primeiro parse, depois square

Function<String, Integer> squareThenParse = parse.compose(square);
// Erro! square recebe Integer e parse espera String

Moral: sempre verifique a ordem de aplicação e a compatibilidade de tipos.

10. Problemas com checked exceptions em lambdas

Interfaces funcionais do pacote java.util.function não permitem lançar exceções checked (por exemplo, IOException). Se dentro da lambda for necessário código que as lance, trate a exceção manualmente.

Function<String, String> readFile = path -> {
    try {
        return Files.readString(Path.of(path));
    } catch (IOException e) {
        throw new RuntimeException(e); // Ou tratar de outra forma
    }
};

Caso contrário, o compilador não permitirá usar tal função em stream ou coleção.

1
Pesquisa/teste
Programação funcional, nível 49, lição 4
Indisponível
Programação funcional
Programação funcional
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION