Da fonte ao resultado
Começa por identificar a fonte, a operação sobre cada elemento e o resultado pedido. Numa lista com "ap", "long" e "cat", filtrar comprimentos de pelo menos três deixa duas palavras. Aplicar String::length transforma essas palavras nos números 4 e 3. A operação terminal toList materializa esses resultados. Nenhuma dessas escolhas pede ordenação alfabética ou eliminação de duplicados.
Uma lambda como s -> s.length() >= 3 descreve uma função; não é uma chamada imediata para todos os elementos. Um pipeline sem operação terminal ainda não calculou o resultado. Mesmo quando existe uma operação terminal, o trabalho necessário depende do resultado solicitado.
Na revisão, escreve o tipo à saída de cada etapa: Stream<String> depois do filtro e Stream<Integer> depois do mapeamento. Isso evita confundir map, que transforma cada resultado, com filter, que decide se um elemento segue. Para fonte ordenada, acompanha também a ordem de encontro; ela não equivale a uma ordenação que nunca foi pedida.
Captura e estado que muda
Uma lambda pode usar parâmetros próprios e valores do contexto. Uma variável local capturada tem de ser final ou effectively final e estar definitivamente atribuída. No exemplo com int limit = 3, criar um Predicate que consulta limit e depois atribuir limit = 5 viola essa regra. Não há uma atualização automática do limite capturado.
Escolhe conscientemente se o cálculo deve usar uma configuração estável ou consultar estado atual. Para uma decisão estável, prepara o valor antes de criar a função e não o reatribuas. Introduzir um array de um elemento apenas para contornar a restrição pode permitir mutação, mas altera o desenho: a função passa a depender de estado partilhado. O compilador aceitar essa referência não prova que a decisão permaneça consistente.
Em pipelines, mantém os predicados e transformações sem alteração da fonte e sem dependências que mudam durante o cálculo. Uma pergunta de diagnóstico deve separar três planos: código aceite pelo compilador, regras do contrato da API e comportamento exigido pelo domínio. Resolver apenas o primeiro não assegura os restantes.
Ausência e cálculo do valor alternativo
Optional representa um valor presente ou ausência. Se uma integração devolve null para um rótulo desconhecido, ofNullable transforma esse resultado em ausência explícita; of pressupõe um valor não nulo. A política de domínio continua por decidir: ausência pode ser aceitável, exigir um valor alternativo ou impedir a operação.
Não confundas o valor devolvido com o trabalho executado para o obter. cached.orElse(load()) avalia load antes de chamar orElse, mesmo que cached já tenha um valor. cached.orElseGet(() -> load()) fornece um cálculo que só é chamado quando necessário. Se load acede a uma dependência ou atualiza um contador, essa diferença afeta custo e efeitos.
Evita usar get sem estabelecer a presença. Uma cadeia de map mantém o resultado opcional e converte um resultado null em vazio; flatMap serve quando a própria função já devolve Optional e não deve acrescentar outra camada. O Optional devolvido por essa função tem de ser não nulo. Estas operações não classificam automaticamente uma falha de serviço como ausência de dados; essa decisão pertence ao contrato da integração.
Recolher, achatar e definir colisões
Um Stream<Optional<String>> ainda contém recipientes, incluindo os vazios. Achatar cada Optional.stream contribui com zero ou um String para o resultado. Não equivale a chamar get em todos, pois esse caminho pode falhar na ausência. A distinção torna-se visível nos tipos: map conserva a camada que a função devolve; flatMap integra os elementos dos streams devolvidos.
Escolhe também o contrato da coleção de saída. Stream.toList produz uma lista não modificável; Collectors.toList não promete uma implementação ou mutabilidade concreta. Se o consumidor precisa de uma ArrayList modificável, explicita essa escolha com Collectors.toCollection(ArrayList::new). Isso não torna os elementos profundamente imutáveis nem elimina duplicados.
Para mapas, uma chave pode representar várias linhas de entrada. A sobrecarga de toMap sem função de combinação rejeita chaves repetidas. Uma função adicional deve refletir o domínio: somar quantidades, rejeitar duplicados ou escolher uma versão com um critério definido são políticas diferentes. Escolher a última linha apenas para fazer desaparecer uma exceção pode perder dados válidos.
Uma resposta correta pode omitir efeitos
Contar quatro eventos não demonstra que quatro registos de auditoria foram escritos. Num pipeline de uma lista estável com peek(audit::record).count(), a implementação pode obter o tamanho sem percorrer os elementos. A contagem continua correta, mas o efeito colocado em peek não é uma obrigação adequada para registar cada evento. Passar o pipeline a sequencial não corrige essa dependência.
Se o requisito é efetuar uma ação em cada elemento, modela essa responsabilidade explicitamente, incluindo como observar falhas e que fazer depois de efeitos parciais. Um ciclo pode ser mais claro quando a ordem, o estado de cada tentativa e a recuperação fazem parte do problema. Nenhuma forma de iteração fornece por si só uma transação sobre um serviço externo.
Um Stream também não é uma coleção reutilizável. Depois de uma operação terminal, cria uma nova travessia a partir da fonte, se ela o permitir. Fontes ligadas a I/O podem exigir fecho; nesse caso a vida útil tem de abranger o consumo. Materializar ou contar resultados não substitui a responsabilidade de gerir o recurso.
Vazio, ordem e agrupamento
No serviço de expedição, “todos os pedidos aprovados” e “existe pelo menos um pedido” são condições diferentes. allMatch devolve true para uma fonte vazia. Isso é coerente com a pergunta universal, mas insuficiente para autorizar um lote que tem de conter trabalho. Sobre uma lista estável, combina a condição de não vazio com a verificação de todos os elementos. anyMatch responde a outra pergunta: se pelo menos um satisfaz o predicado.
A ordem de encontro também não impõe o agrupamento de uma redução paralela. A subtração dá resultados diferentes em (0-2)-3 e 0-(2-3). Usar reduce com essa operação viola o requisito de associatividade; mudar apenas o valor inicial não resolve. Identidade e operação têm de ser compatíveis com a combinação de resultados parciais.
Antes de paralelizar, escreve a regra matemática e os limites dos valores. A soma de inteiros pode servir para quantidades quando a representação e os limites são adequados; médias, arredondamentos e estado mutável exigem análise própria. “Executou mais depressa” não é evidência de equivalência funcional.
Revisão guiada: totais de stock
O excerto agrega linhas imutáveis por SKU. As entradas são estáveis, os SKUs não nulos e cada unidade contribui para o total. Para Q com 3 e 6 unidades e R com 4, o resultado esperado associa Q a 9 e R a 4; a ordem das entradas do mapa não faz parte do contrato.
A função Integer::sum explicita a combinação de quantidades. O caso assume valores não negativos e totais dentro de int; se essa condição deixar de valer, é necessário rever a representação ou detetar excesso antes de publicar. Escolher o último valor não é uma forma alternativa de somar.
O método devolve um mapa novo depois de terminar a recolha. O chamador conserva o relatório ativo até receber o resultado normal e cumprir as restantes verificações. Se o cálculo falhar, não deve apresentar um mapa vazio como relatório válido. Esta separação não promete publicação distribuída atómica. A avaliação pede interpretar o excerto e selecionar respostas; não exige executar código nem produzir ficheiros.
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
final class StockReport {
record Line(String sku, int units) {}
// Stable inputs, non-null SKUs, nonnegative units and totals within int.
static Map<String, Integer> totals(List<Line> lines) {
return lines.stream().collect(Collectors.toMap(
Line::sku, Line::units, Integer::sum));
}
}
Linhas Q=3, Q=6 e R=4 produzem Q=9 e R=4. A lista de entrada não é alterada; a publicação continua a ser responsabilidade do chamador.
Armadilhas comuns
Usar peek para auditoria obrigatória; reutilizar um stream; aprovar vazio com allMatch; chamar fallback sem necessidade; subtrair numa redução paralela.
Tópicos relacionados: Coleções, generics e chaves · Exceções, recursos e ficheiros · Decimais, tempo e fronteiras · Concorrência, estado e cancelamento
A forma do pipeline não prova os efeitos nem a política do domínio. Acompanha tipos, ausência, colisões e requisitos de combinação.
Referência: Java 25 API: Stream · Java SE 25; no preview features