Java Optional

Um método que procura algo pode não achar. Durante muito tempo, em Java, "não achei" era devolvido como null, e quem chamava o método esquecia de conferir: NullPointerException. O Optional (Java 8) é uma caixa que pode estar vazia: o método devolve a caixa, e o tipo Optional<Aluno> já avisa quem chama: "talvez não tenha nada aqui dentro; decida o que fazer".

Você vai aprender
  • O problema do null, com a NullPointerException real.
  • Criar: Optional.of, Optional.ofNullable, Optional.empty.
  • Perguntar e agir: isPresent, isEmpty, ifPresent, ifPresentOrElse.
  • Tirar o valor com segurança: orElse, orElseGet, orElseThrow (e a NoSuchElementException).
  • Transformar sem abrir a caixa: map e filter.
  • De onde vêm os Optionals (findFirst, max) e quando não usar.

O problema: o null

A Ana escreve um método que procura um aluno pelo nome. Se não achar, devolve null ("nada"):

Este código tem um erro de propósito.

import java.util.List;

record Aluno(String nome, double nota) { }

public class Main {
    static List<Aluno> turma = List.of(new Aluno("Ana", 9.5), new Aluno("Bruno", 7.0));

    static Aluno buscar(String nome) {
        for (Aluno a : turma) {
            if (a.nome().equals(nome)) {
                return a;
            }
        }
        return null;
    }

    public static void main(String[] args) {
        System.out.println("Nota da Ana: " + buscar("Ana").nota());
        System.out.println("Nota do Zeca: " + buscar("Zeca").nota());
    }
}
$ javac Main.java
$ java Main
Nota da Ana: 9.5
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Aluno.nota()" because the return value of "Main.buscar(String)" is null
	at Main.main(Main.java:19)

"Não posso chamar Aluno.nota() porque o valor devolvido por Main.buscar(String) é null." A mensagem é clara (o Java ficou bom em explicar NPEs), mas o problema é outro: nada no código avisou que buscar podia devolver null. O tipo de retorno dizia Aluno, e quem chamou confiou. O inventor do null, Tony Hoare, chamou-o de "o erro de um bilhão de dólares".


A ideia: uma caixa com ou sem presente

Imagine que, em vez de entregar o presente na mão (ou entregar o ar, quando não há presente), a loja sempre entrega uma caixa. Às vezes tem algo dentro, às vezes está vazia. Quem recebe sabe que precisa olhar antes de usar, porque está segurando uma caixa, não um presente.

Optional[Aluno Ana]     « caixa com algo dentro
Optional.empty          « caixa vazia

O mesmo método, agora devolvendo Optional<Aluno>:

import java.util.List;
import java.util.Optional;

record Aluno(String nome, double nota) { }

public class Main {
    static List<Aluno> turma = List.of(new Aluno("Ana", 9.5), new Aluno("Bruno", 7.0));

    static Optional<Aluno> buscar(String nome) {
        for (Aluno a : turma) {
            if (a.nome().equals(nome)) {
                return Optional.of(a);
            }
        }
        return Optional.empty();
    }

    public static void main(String[] args) {
        Optional<Aluno> ana = buscar("Ana");
        Optional<Aluno> zeca = buscar("Zeca");
        System.out.println(ana);
        System.out.println(zeca);
        System.out.println("Ana encontrada? " + ana.isPresent());
        System.out.println("Zeca vazio? " + zeca.isEmpty());
    }
}
$ javac Main.java
$ java Main
Optional[Aluno[nome=Ana, nota=9.5]]
Optional.empty
Ana encontrada? true
Zeca vazio? true
import java.util.Optional;
Optional mora em java.util.
Optional<Aluno>
"Talvez um Aluno." É uma classe genérica: o tipo entre < > é o do conteúdo.
Optional.of(a)
Embrulha o aluno numa caixa cheia.
Optional.empty()
Uma caixa vazia.
isPresent() / isEmpty()
"Tem algo?" / "Está vazia?" (isEmpty é do Java 11+).

Agora não dá mais para escrever buscar("Zeca").nota(): Optional não tem método nota(), e o compilador recusa. Você é obrigado a decidir o que fazer com a caixa vazia. As próximas seções mostram como.


Criar um Optional

import java.util.Optional;

public class Main {
    public static void main(String[] args) {
        String apelido = null;
        Optional<String> a = Optional.of("Aninha");
        Optional<String> b = Optional.ofNullable(apelido);
        Optional<String> c = Optional.empty();
        System.out.println(a + " " + b + " " + c);
        Optional<String> d = Optional.of(apelido);
    }
}
$ javac Main.java
$ java Main
Optional[Aninha] Optional.empty Optional.empty
Exception in thread "main" java.lang.NullPointerException
	at java.base/java.util.Objects.requireNonNull(Objects.java:220)
	at java.base/java.util.Optional.of(Optional.java:113)
	at Main.main(Main.java:10)
FormaQuando usarCom null
Optional.of(x)Você tem certeza de que x não é null.NullPointerException na hora (linha 10 acima)
Optional.ofNullable(x)x pode ser null (por exemplo, veio de um mapa.get(chave)).Vira Optional.empty
Optional.empty()Você quer dizer explicitamente "não há nada".—

ofNullable é a ponte entre o código antigo, que devolve null, e o novo: Optional.ofNullable(apelidos.get(nome)).


Agir só se houver valor: ifPresent

import java.util.Optional;

public class Main {
    public static void main(String[] args) {
        Optional<String> cheio = Optional.of("Ana");
        Optional<String> vazio = Optional.empty();

        cheio.ifPresent(n -> System.out.println("Achei: " + n));
        vazio.ifPresent(n -> System.out.println("Achei: " + n));

        cheio.ifPresentOrElse(
            n -> System.out.println("Bem-vinda, " + n),
            () -> System.out.println("Ninguém encontrado"));
        vazio.ifPresentOrElse(
            n -> System.out.println("Bem-vinda, " + n),
            () -> System.out.println("Ninguém encontrado"));
    }
}
$ javac Main.java
$ java Main
Achei: Ana                   « o vazio.ifPresent não fez nada
Bem-vinda, Ana
Ninguém encontrado

ifPresent recebe um Consumer (veja Interfaces Funcionais) e só o executa se houver valor. ifPresentOrElse Java 9+ recebe também um Runnable (uma ação sem parâmetros, () -> …) para o caso vazio.


Tirar o valor da caixa: orElse, orElseGet, orElseThrow

Na maioria das vezes você quer o valor, com um plano B para a caixa vazia:

import java.util.Optional;

public class Main {
    public static void main(String[] args) {
        Optional<String> cheio = Optional.of("Ana");
        Optional<String> vazio = Optional.empty();

        System.out.println("orElse: " + cheio.orElse("visitante") + ", " + vazio.orElse("visitante"));
        System.out.println("orElseGet: " + vazio.orElseGet(() -> "visitante nº " + (40 + 2)));
        System.out.println("orElseThrow: " + cheio.orElseThrow());
        System.out.println("orElseThrow: " + vazio.orElseThrow());
    }
}
$ javac Main.java
$ java Main
orElse: Ana, visitante                 « vazio: usou o padrão
orElseGet: visitante nº 42
orElseThrow: Ana
Exception in thread "main" java.util.NoSuchElementException: No value present
	at java.base/java.util.Optional.orElseThrow(Optional.java:377)
	at Main.main(Main.java:11)
MétodoCaixa cheiaCaixa vazia
orElse(padrao)o valorpadrao
orElseGet(() -> …)o valoro resultado do Supplier (calculado só agora)
orElseThrow() Java 10+o valorNoSuchElementException: No value present
orElseThrow(() -> new MinhaExcecao(…))o valorlança a exceção que você escolher
get()o valorNoSuchElementException (o mesmo que orElseThrow(); nome antigo e enganoso)

orElseThrow() quer dizer "eu garanto que há valor; se não houver, é um erro do programa". Para uma mensagem melhor, crie a sua exceção:

        Optional<String> vazio = Optional.empty();
        String nome = vazio.orElseThrow(() -> new IllegalArgumentException("Aluno não encontrado"));
$ java Main
Exception in thread "main" java.lang.IllegalArgumentException: Aluno não encontrado
	at Main.lambda$main$0(Main.java:6)
	at java.base/java.util.Optional.orElseThrow(Optional.java:403)
	at Main.main(Main.java:6)

A primeira linha da pilha, Main.lambda$main$0, é o nome que o Java dá à sua lambda: "a primeira lambda dentro de main". Veja throw e throws para mais sobre lançar exceções.

orElse × orElseGet: quando o padrão é calculado

Parecem iguais, mas há uma diferença. Veja com um "padrão" que avisa quando é calculado:

import java.util.Optional;

public class Main {
    static String padrao() {
        System.out.println("   (calculando o padrão...)");
        return "visitante";
    }

    public static void main(String[] args) {
        Optional<String> cheio = Optional.of("Ana");
        System.out.println("orElse:");
        System.out.println("   " + cheio.orElse(padrao()));
        System.out.println("orElseGet:");
        System.out.println("   " + cheio.orElseGet(() -> padrao()));
    }
}
$ javac Main.java
$ java Main
orElse:
   (calculando o padrão...)       « calculou à toa: a caixa estava cheia!
   Ana
orElseGet:
   Ana                            « nem chamou padrao()

Com orElse(padrao()), o Java calcula o argumento antes de chamar o método, como sempre faz com argumentos, mesmo que ele não seja usado. Com orElseGet(() -> padrao()), você passa uma lambda, que só é executada se a caixa estiver vazia. Regra: valor pronto ("visitante", 0.0) → orElse; cálculo caro (ler um arquivo, consultar um banco) → orElseGet.

Missão: o Bruno não tem apelido, e o get() quebra o programa com NoSuchElementException. Troque os dois get() por orElse("sem apelido"). A saída deve ser Ana: Aninha e Bruno: sem apelido.

import java.util.Map;
import java.util.Optional;

public class Main {
    static Map<String, String> apelidos = Map.of("Ana", "Aninha");

    static Optional<String> buscarApelido(String nome) {
        return Optional.ofNullable(apelidos.get(nome));
    }

    public static void main(String[] args) {
        System.out.println("Ana: " + buscarApelido("Ana").get());
        System.out.println("Bruno: " + buscarApelido("Bruno").get());
    }
}
import java.util.Map;
import java.util.Optional;

public class Main {
    static Map<String, String> apelidos = Map.of("Ana", "Aninha");

    static Optional<String> buscarApelido(String nome) {
        return Optional.ofNullable(apelidos.get(nome));
    }

    public static void main(String[] args) {
        System.out.println("Ana: " + buscarApelido("Ana").orElse("sem apelido"));
        System.out.println("Bruno: " + buscarApelido("Bruno").orElse("sem apelido"));
    }
}

Transformar sem abrir: map e filter

Muitas vezes você não quer o aluno, e sim a nota dele, se ele existir. Em vez de abrir a caixa, conferir e tirar a nota, use map: ele transforma o conteúdo se houver, e uma caixa vazia continua vazia. É o mesmo map dos streams, para uma caixa de no máximo um elemento.

import java.util.List;
import java.util.Optional;

record Aluno(String nome, double nota) { }

public class Main {
    static List<Aluno> turma = List.of(new Aluno("Ana", 9.5), new Aluno("Bruno", 6.0));

    static Optional<Aluno> buscar(String nome) {
        return turma.stream().filter(a -> a.nome().equals(nome)).findFirst();
    }

    public static void main(String[] args) {
        System.out.println(buscar("Ana").map(Aluno::nota));
        System.out.println(buscar("Ana").map(Aluno::nota).orElse(0.0));
        System.out.println(buscar("Zeca").map(Aluno::nota).orElse(0.0));
        System.out.println(buscar("Bruno").map(a -> a.nome().toUpperCase()).orElse("?"));
        System.out.println(buscar("Bruno").filter(a -> a.nota() >= 7).map(Aluno::nome).orElse("ninguém aprovado"));
    }
}
$ javac Main.java
$ java Main
Optional[9.5]                 « caixa de aluno virou caixa de nota
9.5
0.0                           « Zeca não existe: map não fez nada, orElse deu 0.0
BRUNO
ninguém aprovado              « o filter esvaziou a caixa: nota 6.0 < 7
buscar(…) com findFirst()
Repare que o buscar agora é um stream de uma linha: findFirst já devolve um Optional.
.map(Aluno::nota)
De Optional<Aluno> para Optional<Double>.
.filter(a -> a.nota() >= 7)
Se o conteúdo não passar no teste, a caixa fica vazia.
.orElse(…)
No fim da corrente, decide o que fazer se a caixa chegou vazia.

Esse estilo, uma corrente terminando em orElse, é o uso mais idiomático do Optional: nenhum if, nenhum null.


De onde vêm os Optionals

Você raramente cria Optionals: na maior parte do tempo, você os recebe da biblioteca, de operações que podem não ter resposta:

import java.util.Comparator;
import java.util.List;
import java.util.Optional;

record Aluno(String nome, double nota) { }

public class Main {
    public static void main(String[] args) {
        List<Aluno> turma = List.of(new Aluno("Ana", 9.5), new Aluno("Bruno", 6.0), new Aluno("Carla", 8.0));
        List<Aluno> vazia = List.of();

        Optional<Aluno> melhor = turma.stream().max(Comparator.comparing(Aluno::nota));
        System.out.println("max: " + melhor);
        System.out.println("max da turma vazia: " + vazia.stream().max(Comparator.comparing(Aluno::nota)));
        System.out.println("findFirst com C: " + turma.stream().filter(a -> a.nome().startsWith("C")).findFirst());
        System.out.println("min nota: " + turma.stream().map(Aluno::nota).min(Double::compare).orElseThrow());
    }
}
$ javac Main.java
$ java Main
max: Optional[Aluno[nome=Ana, nota=9.5]]
max da turma vazia: Optional.empty          « turma vazia não tem melhor aluno
findFirst com C: Optional[Aluno[nome=Carla, nota=8.0]]
min nota: 6.0
OrigemDevolve
stream.findFirst(), findAny()Optional<T>
stream.max(comp), min(comp)Optional<T>
stream.reduce(op) (sem valor inicial)Optional<T>
intStream.average(), max()OptionalDouble, OptionalInt (com getAsDouble(), getAsInt(), orElse)
Seus métodos de buscaOptional<T>, quando "não achei" é um resultado normal

Quando não usar Optional

O Optional foi criado para um uso específico: o retorno de métodos que podem não ter resultado. Fora disso, ele mais atrapalha do que ajuda:

Não façaPor quêFaça
Optional<List<Aluno>> buscarTodos()Uma lista já sabe ser "vazia".Devolva List.of() (lista vazia).
void matricular(Optional<String> apelido)Quem chama precisa embrulhar tudo; parâmetros opcionais ficam confusos.Sobrecarga: matricular() e matricular(String apelido).
private Optional<String> apelido; (atributo)Gasta memória e não foi feito para isso.Um atributo comum (que pode ser null) e um getter que devolve Optional.ofNullable(apelido).
if (op.isPresent()) { x = op.get(); }É só um if (x != null) mais comprido.op.orElse(…), op.ifPresent(…), op.map(…).
op.get() sem conferirTroca a NullPointerException por NoSuchElementException: o mesmo erro com outro nome.orElse ou orElseThrow com mensagem.
Optional<Integer> op = null;A caixa em si nunca deve ser null: era isso que se queria evitar.Optional.empty().

Quando dá errado

NoSuchElementException: No value present
Exception in thread "main" java.util.NoSuchElementException: No value present
	at java.base/java.util.Optional.get(Optional.java:143)
	at Main.main(Main.java:13)

O que aconteceu: get() ou orElseThrow() numa caixa vazia. A primeira linha da pilha diz qual dos dois: aqui, Optional.get.

Como consertar: decida o que fazer com o vazio: orElse(padrão), ifPresent(…), ou, se o vazio for mesmo um erro, orElseThrow(() -> new …("mensagem clara")).

NullPointerException em Optional.of
Exception in thread "main" java.lang.NullPointerException
	at java.base/java.util.Objects.requireNonNull(Objects.java:220)
	at java.base/java.util.Optional.of(Optional.java:113)
	at Main.main(Main.java:10)

O que aconteceu: Optional.of(x) com x valendo null.

Como consertar: se o valor pode ser null, use Optional.ofNullable(x).

"cannot find symbol" ao chamar um método do conteúdo
$ javac Main.java
Main.java:12: error: cannot find symbol
        System.out.println(buscar("Ana").nota());
                                        ^
  symbol:   method nota()
  location: class Optional<Aluno>
1 error

O que aconteceu: buscar devolve Optional<Aluno>, e a classe Optional não tem nota(): ela é a caixa, não o aluno.

Como consertar: é o Optional fazendo o trabalho dele. Use buscar("Ana").map(Aluno::nota).orElse(0.0).


Referência

MétodoO que faz
Optional.of(x) / ofNullable(x) / empty()Criar (cheia / cheia ou vazia / vazia)
isPresent() / isEmpty()Tem valor? / Está vazia?
ifPresent(c) / ifPresentOrElse(c, r)Executa só se houver valor (/ senão, outra ação)
orElse(padrao)O valor, ou o padrão (sempre calculado)
orElseGet(() -> …)O valor, ou o resultado da lambda (calculado só se vazia)
orElseThrow() / orElseThrow(() -> exc)O valor, ou exceção
get()O valor, ou NoSuchElementException (prefira orElseThrow)
map(f)Transforma o conteúdo, se houver
filter(p)Esvazia se o conteúdo não passar no teste
or(() -> outroOptional) Java 9+Se vazia, tenta outro Optional
stream() Java 9+Stream de 0 ou 1 elemento

Exercícios

1. Pode ser null

Complete para criar um Optional a partir de email, que pode ser null:

Optional<String> op = Optional.;

2. Plano B

Complete para obter o texto, ou "sem e-mail" se estiver vazio:

String s = op.;

3. Transformar

Complete para transformar um Optional<Aluno> no Optional do nome dele:

Optional<String> nome = aluno.;

4. O que Optional.empty().orElseThrow() faz?

5. Num Optional cheio, qual destes executa padrao() mesmo sem precisar?

6. Um método que devolve "todos os alunos de uma cidade" deve devolver…

Missão final: o programa quebra com NoSuchElementException ao procurar o Zeca. Troque .get().nota() por .map(Aluno::nota).orElse(0.0). A saída deve ser Carla: 8.0 e Zeca: 0.0.

import java.util.List;
import java.util.Optional;

record Aluno(String nome, double nota) { }

public class Main {
    static List<Aluno> turma = List.of(new Aluno("Ana", 9.5), new Aluno("Carla", 8.0));

    static Optional<Aluno> buscar(String nome) {
        return turma.stream().filter(a -> a.nome().equals(nome)).findFirst();
    }

    public static void main(String[] args) {
        for (String nome : List.of("Carla", "Zeca")) {
            double nota = buscar(nome).get().nota();
            System.out.println(nome + ": " + nota);
        }
    }
}
import java.util.List;
import java.util.Optional;

record Aluno(String nome, double nota) { }

public class Main {
    static List<Aluno> turma = List.of(new Aluno("Ana", 9.5), new Aluno("Carla", 8.0));

    static Optional<Aluno> buscar(String nome) {
        return turma.stream().filter(a -> a.nome().equals(nome)).findFirst();
    }

    public static void main(String[] args) {
        for (String nome : List.of("Carla", "Zeca")) {
            double nota = buscar(nome).map(Aluno::nota).orElse(0.0);
            System.out.println(nome + ": " + nota);
        }
    }
}

Resumo

  • Optional<T> é uma caixa que pode estar vazia; o tipo avisa quem chama que o resultado pode não existir.
  • Crie com of (nunca null), ofNullable (pode ser null) ou empty.
  • Aja com ifPresent/ifPresentOrElse; tire o valor com orElse, orElseGet ou orElseThrow.
  • orElse sempre calcula o padrão; orElseGet só quando precisa.
  • map e filter transformam sem abrir; termine a corrente com orElse.
  • Vêm de findFirst, max, min, reduce… Use em retornos, não em atributos, parâmetros ou no lugar de coleções vazias.
  • get()/orElseThrow() numa caixa vazia: NoSuchElementException: No value present.

Fim do grupo Programação Funcional. Próxima aula: Pattern Matching.