JAVA Learning

Hub de Conteúdo

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:

java
// 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:

java
// 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
java
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
java
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.

java
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.

java
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.

java
// 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.

java
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

java
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

java
// 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

java
// 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

java
// 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 e acesse número com flatMap().

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! ✨