Dominando o Optional e Stream API no Ecossistema Java

O Optional<T> é uma estrutura projetada para ser utilizada como retorno de métodos onde o valor pode estar ausente, atuando como um susbtituto mais seguro para retornar null. Ele promove uma programação funcional mais limpa.

Criação de Instâncias

Existem três métodos principais para obter uma instância dessa classe:

  • Optional.of(T value): Empacota um valor existente. Lança exceção se o valor fornecido for nulo.
  • Optional.empty(): Retorna uma instância vazia sem conteúdo.
  • Optional.ofNullable(T value): Cria um Optional contendo o valor se ele não for null, caso contrário, retorna uma instância vazia (equivalente ao empty).

Métodos de Consumo e Acesso

Após a instanciação, diversos métodos permitem trabalhar com o dado interno:

  • isPresent(): Retorna verdadeiro caso haja um valor presente.
  • get(): Acessa o valor contido, lançando NoSuchElementException se vazio.
  • ifPresent(Consumer consumer): Executa a ação lambda apenas se o valor existir.
  • orElse(T other): Retorna o valor interno ou um padrão fixo em caso de ausência.
  • orElseGet(Supplier supplier): Semelhante ao anterior, mas aproveita a lazy evaluasion para calcular o padrão apenas quando necessário.
  • orElseThrow(Supplier exceptionSupplier): Permite lançar uma exceção customizada caso o Optional esteja vazio.
  • map(Function mapper): Aplica uma transformação ao valor presente e retorna um novo Optional com o resultado.
  • filter(Predicate predicate): Verifica uma condição; se falhar, retorna Optional vazio.

Estrutura de Dados Stream

A API Stream permite expressar operações de manipulação de dados em coleções de maneira declarativa. É crucial distinguir os dois tipos de operações disponíveis:

  • Intermediárias: Retornam um novo fluxo de dados, permitindo encadeamento (lazy evaluation). Ex: filter, map.
  • Terminais: Consumem o fluxo e retornam um valor final ou efeito colateral. Após executar uma operação terminal, o fluxo é fechado e não pode ser reutilizado.

Geração de Streams

Vários caminhos permitem criar fluxos de entrada:

// Criando partir de sequência de valores
Stream<Integer> numeros = Stream.of(10, 20, 30);

// Iterando com lógica infinita limitada
Stream.iterate(0, x -> x + 5).limit(4).forEach(System.out::println);

// Produzindo valores aleatórios
Stream.generate(Math::random).limit(5);

// A partir de Arrays
Stream.of(new Integer[]{1, 2}).forEach(System.out::print);

// Vindas de Coleções
List<String> lista = new ArrayList<>();
lista.stream().forEach(System.out::print);

// Manipulação de Arquivos
try (Stream<String> linhas = Files.lines(Paths.get("arquivo.txt"))) {
    linhas.forEach(System.out::println);
}

Operações Comuns na Pipeline

Verificação e Contagem

// Verifica se todos atendem à condição
boolean totalValido = itens.stream().allMatch(p -> p.getPreco() > 0);

// Conta elementos específicos
long total = itens.stream().count();

Filtragem e Ordenação

// Remove duplicatas
itens.stream().distinct();

// Filtro condicional
itens.stream().filter(p -> p.isAtivo());

// Ordenação simples ou personalizada
itens.sorted();
itens.sorted((p1, p2) -> p1.getId().compareTo(p2.getId()));

// Pular os N primeiros
itens.skip(10);

// Limitar quantidade
itens.limit(5);

Mapeamento e Achatamento

// Transformação simples
Stream<String> nomes = pessoas.stream().map(Person::getNome);

// Unir listas internas em uma única sequência plana
ListaDeListas.stream().flatMap(Collection::stream);

Busca e Redução

// Obter primeiro elemento (ordenado garantido)
Optional<T> primeiro = itens.stream().findFirst();

// Obter qualquer elemento (otimização para paralelismo)
Optional<T> qualquerUm = itens.stream().findAny();

// Encontrar máximo/mínimo
Optional<Double> maiorPreco = itens.stream().max(Comparator.comparing(Item::getPreco));

// Acumular valor (reduce)
double totalVendas = itens.stream().mapToDouble(Item::getTotal).sum();
// Ou usando reduce genérico
itens.stream().reduce(new Item(), (acc, item) -> acc, (i1, i2) -> i1.merge(i2));

Iteração

// Processar cada item
itens.forEach(item -> System.out.println(item.getId()));

// Observar estado durante o pipeline (debug)
itens.peek(item -> logger.info("Processando: {}", item));

Coletores (Collectors)

A etapa final geralmente utiliza collect() para converter o fluxo em estruturas concretas como List, Set ou Map.

Coletores Básicos

List<String> listaObjetos = stream.collect(Collectors.toList());
Set<String&gt> conjunto = stream.collect(Collectors.toSet());

// Conversão para tipo específico de Collection
stream.collect(Collectors.toCollection(LinkedList::new));

Transformação em Map

Pode haver colisão de chaves, exigindo tratamento adequado.

Map<Long, String> mapaIds = 
    pessoas.stream().collect(
        Collectors.toMap(
            Person::getId,      // Chave
            Person::getNome     // Valor
        )
    );
    
// Tratando colisão de chave
Map<Long, Person> uniqueMap = stream.collect(
    Collectors.toMap(
        Person::getId, 
        Function.identity(),
        (oldValue, newValue) -> oldValue // Mantém o antigo
    )
);

Agrupamento e Particionamento

// Agrupar por categoria
Map<Categoria, List<Produto>> agrupado = produtos.stream()
    .collect(Collectors.groupingBy(Produto::getCategoria));

// Separar em booleanos (sim/não)
Map<Boolean, List<Usuario>> ativosInativos = usuarios.stream()
    .collect(Collectors.partitioningBy(Usuario::isAtivo));

Agregação Numérica

// Média
Double mediaIdades = pessoas.stream()
    .collect(Collectors.averagingInt(Person::getIdade));

// Soma, Mínima, Máxima
int somaQtd = itens.stream().collect(Collectors.summingInt(Item::getQtd));

// Estatísticas completas (máx, mín, média, soma, count)
IntSummaryStatistics stats = numeros.stream()
    .collect(Collectors.summarizingInt(n -> n));

Concatenação de Strings

// Juntar nomes com separador
String csvNomes = nomes.stream()
    .collect(Collectors.joining(", ", "[", "]"));

Fluxos Paralelos

Para processamento intensivo, é possível usar threads internas automaticamente geridas pelo ForkJoinPool.

// Convertendo um stream sequencial em paralelo
collection.stream().parallel().forEach(System.out::println);

// Ou criando diretamente via método factory
collection.parallelStream().forEach(...);

Tags: java stream-api optional java8 Lambda

Publicado em 9-10 22:06