Optional em Java
Diga adeus ao NullPointerException - Trate valores ausentes com elegância
1. O que é Optional?
Optional é um container que pode conter ou não um valor. Ele foi introduzido no Java 8 para evitar verificações nulas e tornar o código mais legível.
Ao invés de retornar null, você retorna um Optional que deixa explícito que o valor pode estar ausente.
O Optional força o desenvolvedor a tratar o caso onde o valor não existe, evitando surpresas com NullPointerException.
2. O Problema com null
NullPointerException é um dos maiores vilões do Java. Sem Optional, é fácil esquecer de verificar null e causar crashes.
❌ Problema - Sem Optional:
// PERIGOSO - Sem verificação pode gerar NullPointerException
public Pessoa buscarPessoa(int id) {
// Retorna null se não encontrar
return banco.encontrar(id);
}
// USO - Fácil esquecer de verificar null
Pessoa p = buscarPessoa(1);
System.out.println(p.getNome()); // CRASH se p for null!
✅ Solução - Com Optional:
// SEGURO - Deixa claro que o valor pode não existir
public Optional buscarPessoa(int id) {
return Optional.ofNullable(banco.encontrar(id));
}
// USO - Obriga a tratar o caso ausente
Optional p = buscarPessoa(1);
if (p.isPresent()) {
System.out.println(p.get().getNome());
}
3. Criando Optional
Métodos Estáticos:
- Optional.of(T valor) - Cria Optional com valor (lança exceção se null)
- Optional.ofNullable(T valor) - Cria Optional com ou sem valor
- Optional.empty() - Cria Optional vazio
import java.util.Optional;
// 1. Optional com valor - seguro, não é null
Optional opt1 = Optional.of("Java");
// 2. Optional que pode ser null
String nome = null;
Optional opt2 = Optional.ofNullable(nome); // Vazio
// 3. Optional vazio explicitamente
Optional opt3 = Optional.empty();
// Verificar se tem valor
System.out.println(opt1.isPresent()); // true
System.out.println(opt2.isPresent()); // false
System.out.println(opt3.isPresent()); // false
// Verificar se está vazio (Java 11+)
System.out.println(opt2.isEmpty()); // true
4. Acessando Valores
Métodos de Acesso:
- get() - Retorna o valor ou lança exceção
- orElse(T outro) - Retorna valor ou padrão
- orElseGet(Supplier) - Valor ou chamada função
- orElseThrow() - Valor ou lança exceção
Optional opt = Optional.ofNullable(obterNome());
// 1. get() - perigoso se vazio
try {
String nome = opt.get(); // Lança NoSuchElementException se vazio
} catch (NoSuchElementException e) {
System.out.println("Valor não existe");
}
// 2. orElse() - melhor - retorna padrão
String nome1 = opt.orElse("Desconhecido");
// 3. orElseGet() - padrão computado
String nome2 = opt.orElseGet(() -> gerarNomePadrao());
// 4. orElseThrow() - explícito
String nome3 = opt.orElseThrow(() ->
new IllegalArgumentException("Nome obrigatório")
);
5. Transformando Valores com map()
map() permite transformar o valor dentro do Optional sem verificar null.
Optional nome = Optional.of("java");
// map() transforma o valor se existir
Optional tamanho = nome.map(String::length);
System.out.println(tamanho.orElse(0)); // 4
// Encadeamento de maps
Optional resultado = Optional.of("Java Programming")
.map(String::toUpperCase)
.map(s -> s.replace(" ", "_"));
System.out.println(resultado.get()); // JAVA_PROGRAMMING
// Map retorna Optional vazio se original estiver vazio
Optional vazio = Optional.empty();
Optional tam = vazio.map(String::length);
System.out.println(tam.orElse(-1)); // -1
6. Filtrando com filter()
filter() permite validar o valor com uma condição.
Optional idade = Optional.of(25);
// filter() mantém se condição é verdadeira
Optional adulto = idade.filter(i -> i >= 18);
System.out.println(adulto.orElse(0)); // 25
// filter() retorna vazio se condição é falsa
Optional crianca = idade.filter(i -> i < 18);
System.out.println(crianca.orElse(-1)); // -1
// Exemplo prático
Optional email = Optional.of("usuario@email.com");
Optional emailValido = email
.filter(e -> e.contains("@"))
.filter(e -> e.contains("."));
if (emailValido.isPresent()) {
System.out.println("Email válido: " + emailValido.get());
}
7. Achatando com flatMap()
flatMap() é para quando você tem um Optional dentro de outro Optional.
// map() criaria Optional>
// flatMap() cria apenas Optional
class Pessoa {
private Optional endereco;
public Optional getEndereco() {
return endereco;
}
}
class Endereco {
private String rua;
public String getRua() {
return rua;
}
}
// ❌ Errado - map() retorna Optional>
Optional pessoa = buscarPessoa(1);
Optional> ruaErrada = pessoa
.map(p -> p.getEndereco())
.map(e -> e.getRua()); // Errado!
// ✅ Correto - flatMap() achata
Optional rua = pessoa
.flatMap(p -> p.getEndereco())
.map(e -> e.getRua());
System.out.println(rua.orElse("Sem endereço"));
8. Ações com ifPresent()
ifPresent() e ifPresentOrElse() executam código baseado na presença do valor.
Optional usuario = Optional.ofNullable(obterUsuario());
// ifPresent() - executa se valor existe
usuario.ifPresent(u -> System.out.println("Bem-vindo, " + u));
// ifPresentOrElse() - executa um ou outro
usuario.ifPresentOrElse(
u -> System.out.println("Usuário: " + u),
() -> System.out.println("Nenhum usuário logado")
);
// Exemplo real - salvar dados
Optional email = buscarEmail();
email.ifPresent(e -> {
salvarBackup(e);
enviarConfirmacao(e);
System.out.println("Email processado");
});
9. Exemplo Prático Completo
class Usuario {
private int id;
private String nome;
private String email;
public Usuario(int id, String nome, String email) {
this.id = id;
this.nome = nome;
this.email = email;
}
public String getNome() { return nome; }
public String getEmail() { return email; }
}
class RepositorioUsuario {
private Map usuarios = new HashMap<>();
public RepositorioUsuario() {
usuarios.put(1, new Usuario(1, "João", "joao@email.com"));
usuarios.put(2, new Usuario(2, "Maria", "maria@email.com"));
}
// Retorna Optional - deixa claro que pode não encontrar
public Optional buscar(int id) {
return Optional.ofNullable(usuarios.get(id));
}
// Usar Optional no dia a dia
public void exibirUsuario(int id) {
buscar(id)
.map(Usuario::getNome)
.ifPresentOrElse(
nome -> System.out.println("Usuário encontrado: " + nome),
() -> System.out.println("Usuário não encontrado")
);
}
public void enviarEmail(int id) {
buscar(id)
.map(Usuario::getEmail)
.filter(e -> e.contains("@"))
.ifPresent(email -> {
System.out.println("Enviando email para: " + email);
// enviar(email);
});
}
}
// USO
RepositorioUsuario repo = new RepositorioUsuario();
repo.exibirUsuario(1); // Usuário encontrado: João
repo.exibirUsuario(999); // Usuário não encontrado
repo.enviarEmail(2); // Enviando email para: maria@email.com
10. Boas Práticas com Optional
✓ FAÇA:
- Use Optional para retornos que podem ser vazios
- Use map(), filter(), flatMap() para transformações
- Use orElse() ou orElseGet() para valores padrão
- Use ifPresent() e ifPresentOrElse() para efeitos colaterais
- Encadeie operações quando possível
✗ EVITE:
- Não use get() sem verificar isPresent() primeiro
- Não use Optional como parâmetro de função
- Não use Optional para valores primitivos (use OptionalInt, etc)
- Não use Optional como campo de classe
- Não use Optional.get() sem tratamento
11. Erros Comuns
❌ Erro 1: Usar get() sem verificar
// ERRADO - NullPointerException!
Optional opt = Optional.empty();
String valor = opt.get(); // Lança NoSuchElementException
// CORRETO
String valor = opt.orElse("padrão");
❌ Erro 2: Optional como parâmetro
// ERRADO - Não use Optional em parâmetros
public void processar(Optional dados) { }
// CORRETO - Deixe o chamador decide
public void processar(String dados) { }
// Se pode ser null, documente
public void processar(@Nullable String dados) { }
❌ Erro 3: Esquecer de tratar Optional vazio
// ERRADO - Pode não fazer nada
Optional resultado = buscar();
resultado.ifPresent(r -> salvar(r));
// E se estiver vazio?
// CORRETO - Trate ambos os casos
resultado.ifPresentOrElse(
this::salvar,
() -> System.out.println("Nada para salvar")
);
12. Exercícios Práticos
Exercício 1: Método com Optional
Crie um método que busque um produto por ID e retorne Optional. Trate os casos presença e ausência.
Exercício 2: Encadeamento
Use map() e filter() para transformar e validar um email dentro de um Optional.
Exercício 3: FlatMap
Crie classes Pessoa e Telefone onde Pessoa tem Optional
Exercício 4: Padrão Padrão
Implemente orElse(), orElseGet() e orElseThrow() no mesmo contexto.
Exercício 5: Comparar null vs Optional
Reescreva um código antigo que usa null em um novo que usa Optional.
Conclusão
Optional é uma ferramenta poderosa para evitar NullPointerException e tornar o código mais seguro e expressivo.
Use map(), filter(), flatMap() para transformações elegantes e ifPresent() para efeitos colaterais.
Domine Optional e seu código Java será muito mais robusto e profissional! ✨