W3docs

Java Optional

Expresse a possível ausência de um valor em Java com Optional e evite NullPointerException por design.

Optional<T> é um contêiner que armazena ou um valor do tipo T ou nada — e indica qual dos dois, no nível do tipo, para que o compilador possa forçá-lo a tratar o caso ausente. Foi adicionado no Java 8 junto com streams, e os dois são projetados para funcionar juntos: findFirst, findAny, min, max, reduce retornam Optional<T> precisamente porque a resposta pode não existir, e a API oferece formas fluentes de continuar calculando sem precisar escrever if (x != null).

Optional não é um substituto de null em todo lugar, e o JDK tem uma opinião definida sobre onde ele pertence. Este capítulo percorre a API do início ao fim, seguido pelos três lugares em que Optional é a escolha errada.

Construindo um Optional

Três construtores, cada um com um significado preciso:

Optional<String> a = Optional.of("hello");           // present; null arg throws NPE
Optional<String> b = Optional.empty();                // absent
Optional<String> c = Optional.ofNullable(maybeNull);  // present if non-null, else empty

A distinção importa. Optional.of(x) é a afirmação "este valor definitivamente está aqui" — se você passar null, ele lança NullPointerException imediatamente, o que é o desejado (um bug exposto na origem, não três níveis abaixo). Optional.ofNullable(x) é o adaptador que você usa para envolver uma API legada que retorna null para "ausente."

Você quase nunca constrói um Optional manualmente dentro de um pipeline de stream — terminais como findFirst e Collectors.maxBy os produzem para você.

Verificando se um valor está presente

As duas consultas:

Optional<String> opt = lookup(id);
boolean has = opt.isPresent();      // true if a value is held
boolean none = opt.isEmpty();        // Java 11+ -- the opposite of isPresent

Você verá esses métodos em código de produção, mas geralmente são um sinal de código ruim: a maioria do código que chama isPresent e depois get ficaria melhor com um dos métodos operate-on-it abaixo. Os métodos de consulta servem para código de fronteira onde você realmente precisa de um boolean — uma cláusula de guarda, uma decisão de rota, um ramo de aviso no log.

Lendo o valor com segurança

A forma errada:

String name = opt.get();   // throws NoSuchElementException if empty

opt.get() é a leitura sem verificação. É como você transforma um Optional de volta em um valor e uma exceção em tempo de execução, exatamente o que o tipo deveria prevenir. Use-o somente depois de provar que o optional está presente (ou após findFirst().orElseThrow() de um pipeline onde o resultado vazio seria um bug de programador, não um caso esperado).

As formas corretas, em ordem de preferência:

String name1 = opt.orElse("anonymous");                          // default value
String name2 = opt.orElseGet(() -> expensiveDefault());          // lazy default
String name3 = opt.orElseThrow();                                 // NoSuchElementException
String name4 = opt.orElseThrow(() -> new MyDomainError(id));      // custom exception
  • orElse(value) — fornece um padrão. O valor é sempre avaliado, mesmo quando o optional está presente, portanto não passe uma expressão cara.
  • orElseGet(supplier) — fornece um padrão lazily. O supplier só é executado quando o optional está vazio. Use isso para qualquer padrão que custe mais do que um literal.
  • orElseThrow() — lança NoSuchElementException se ausente. A forma sem argumentos do Java 10+ é o equivalente moderno de opt.get() quando "isso absolutamente deveria estar presente" é a única interpretação sensata no ponto de chamada.
  • orElseThrow(supplier) — lança uma exceção específica do domínio. A forma padrão de traduzir "ausente" em "404 não encontrado."

Transformando o valor — map

Se o optional estiver presente, aplica uma função; caso contrário, permanece vazio:

Optional<String> upper = opt.map(String::toUpperCase);
Optional<Integer> len   = opt.map(String::length);

A assinatura é Optional<T>.map(Function<T, R>) -> Optional<R>. A função só é executada quando um valor está presente — sem verificação de null, sem if, e sem else. Esta é a operação que torna o Optional valioso: a maioria das cadeias "se não nulo, faça isso; se não nulo, depois faça isso" se reduz a .map(...).map(...).map(...).

Há um caso especial que o JDK trata silenciosamente: se sua função map retornar null (porque encapsula uma API legada que retorna null para "sem resultado"), o Optional resultante é empty() — não Optional.of(null).

Compondo optionals — flatMap

Quando a função de mapeamento ela mesma retorna um Optional, map produziria Optional<Optional<T>>. flatMap achata isso:

record User(String id, Optional<Address> address) {}
record Address(String city) {}

Optional<String> city = userById(id)
    .flatMap(User::address)        // Optional<Address>
    .map(Address::city);            // Optional<String>

flatMap é a operação que permite encadear várias consultas, cada uma das quais pode falhar, em um único pipeline. Ambos os casos de falha colapsam para Optional.empty() no final, e o consumidor os trata uma única vez com orElse / orElseThrow.

Filtrando — filter

Testa o valor contra um Predicate<T>; retorna o mesmo optional se passar, empty() se não passar:

Optional<String> nonBlank = opt.filter(s -> !s.isBlank());
Optional<Integer> positive = numberOpt.filter(n -> n > 0);

Age como um guarda dentro do pipeline optional. Útil quando a pergunta é "tenho um valor, mas é o valor certo para continuar?"

Efeitos colaterais — ifPresent, ifPresentOrElse

Executa código somente quando o valor está presente:

opt.ifPresent(name -> log.info("hello, {}", name));

Ou executa um ramo quando presente e outro quando vazio (Java 9+):

opt.ifPresentOrElse(
    name -> log.info("hello, {}", name),
    () -> log.warn("no name on the request"));

Estas são as formas corretas de expressar "faça algo ao passar." Elas substituem completamente o padrão if (opt.isPresent()) { use(opt.get()); }.

Integrando com streams — Optional.stream()

(Java 9+) Transforma um Optional<T> em um Stream<T> de zero ou um elemento:

Stream<String> s = opt.stream();

Útil dentro de flatMap em um Stream<Optional<T>>:

List<String> presentCities = userIds.stream()
    .map(this::userById)           // Stream<Optional<User>>
    .flatMap(Optional::stream)      // Stream<User>     -- empties drop, presents pass through
    .map(User::city)
    .toList();

Isso substitui filter(Optional::isPresent).map(Optional::get) por um único flatMap(Optional::stream). Mesmo resultado, pipeline mais limpo.

or — recorrer a outro Optional

(Java 9+) Se vazio, usa um supplier de outro Optional:

Optional<User> u = primaryLookup(id)
    .or(() -> fallbackLookup(id))
    .or(() -> Optional.of(User.anonymous()));

Lê-se como "tente o primário; se ausente, tente o alternativo; se ausente, use anônimo." Os três são Optional<User>; a cadeia retorna o primeiro não vazio. Diferente de orElseor mantém o resultado encapsulado; orElse o desencapsula com um padrão simples T.

Especializações primitivas

Existem OptionalInt, OptionalLong, OptionalDouble para resultados primitivos — o que IntStream.max() retorna, por exemplo:

OptionalInt max = nums.stream().mapToInt(Integer::intValue).max();
int hi = max.orElse(0);

Eles têm uma API menor — sem map/flatMap/filter — porque ficam na fronteira do mundo primitivo. Use-os para ler resultados de streams primitivos; converta para Optional<Integer> se precisar da API completa.

Onde Optional não pertence

A intenção de design do JDK é restrita: Optional é um tipo de retorno para métodos cuja resposta pode não existir. Ele não é:

  • Um tipo de campo. Não escreva private Optional<String> middleName;. Não é Serializable, custa uma alocação por campo, e um campo null é mais curto e mais claro para "esta entidade não tem nome do meio." A solução correta é um campo não-Optional que pode ser null, com um getter que retorna Optional.
  • Um parâmetro de método. Não aceite Optional<String> como argumento. Sobrecarregue o método, ou aceite String e documente que null significa ausente. Parâmetros Optional exigem que o chamador encapsule, o que é ruído.
  • Um elemento de coleção. List<Optional<T>> é quase sempre uma lista com elementos anuláveis e encapsulamento extra. Use List<T> e filtre os nulos na fronteira, ou use flatMap(Optional::stream) para descartar os ausentes em um pipeline.
  • Uma forma de evitar todo null. Java ainda tem null em todo tipo de referência; Optional serve para o formato de retorno de código que produz valores que podem não existir. Tipos de referência simples são adequados para todo o resto.

A regra mais curta: um Optional saindo de um método é bom design; um Optional entrando em um método está quase sempre errado.

Um exemplo completo: todos os métodos e as regras práticas em código

O programa abaixo constrói um pequeno grafo de usuário/endereço, percorre todos os métodos de Optional, demonstra o tempo de avaliação de orElse vs. orElseGet, a ponte Optional.stream(), e a cadeia or.

java— editable, runs on the server

O que tirar da execução:

  • Os três construtores of, empty, ofNullable mapeiam para três intenções precisas: definitivamente presente, definitivamente ausente, e adaptador-legado, presente-se-não-nulo. Optional.of(null) lança — e essa é a falha desejada, não um bug a contornar.
  • orElse avaliou seu argumento todas as vezes, mesmo quando o optional estava presente. O supplier de orElseGet só foi executado quando necessário. Use orElse para literais baratos e orElseGet para qualquer coisa que aloque, consulte ou lance.
  • map e flatMap tornaram toda a cadeia userById(...).flatMap(User::address).map(Address::city) legível como um único pipeline — sem verificações de null, sem ifs aninhados, e qualquer etapa vazia faz curto-circuito para Optional.empty() no final.
  • flatMap(Optional::stream) transformou um Stream<Optional<User>> em um Stream<User> com todos os ausentes descartados de uma vez. Essa é a forma limpa de converter uma lista de consultas "pode falhar" em um stream de sucessos.
  • OptionalInt é o que terminais de streams primitivos como IntStream.findFirst retornam. Tem sua própria pequena API (getAsInt, orElse, ifPresent) e existe para que pipelines primitivos nunca precisem encaixotar.
  • A regra de uso incorreto apareceu implicitamente: User.address era um campo Optional<Address> — tudo bem porque o exemplo queria demonstrar a API, mas em código de produção o campo seria um Address possivelmente nulo com um getter Optional<Address> address() fazendo o encapsulamento.

O que vem a seguir

A Parte 12 cobriu o vocabulário funcional do início ao fim: interfaces funcionais, lambdas, referências de método, os built-ins, o pipeline de stream, cada fonte, cada intermediário, cada terminal, coletores, execução paralela e, finalmente, Optional como a expressão em nível de tipo da ausência. O próximo capítulo, Java Predicate Interface, faz um zoom em uma única interface funcional — Predicate<T> — e a álgebra de combinadores (and, or, negate, isEqual, not) que permite montar predicados sem precisar escrever a lógica boolean manualmente. A partir daí, a parte continua com Function, Consumer/Supplier, e a família de operadores binários — uma interface por capítulo, cada uma com o mesmo formato de exemplo trabalhado que você viu aqui.

Prática

Prática
Você tem `Optional<String> opt` e precisa de um padrão quando está vazio, onde o padrão é uma chamada cara a `loadDefaultFromDb()`. Qual é a forma correta *e* evita executar a chamada cara quando `opt` está presente?
Você tem `Optional<String> opt` e precisa de um padrão quando está vazio, onde o padrão é uma chamada cara a `loadDefaultFromDb()`. Qual é a forma correta *e* evita executar a chamada cara quando `opt` está presente?
Was this page helpful?