Java e OO

Chega de NullPointerException! Use a classe Optional

Tenho certeza que em algum momento da sua vida como programador, já passou vergonha com um feio NullPointerException, ou estou mentindo? :)

Neste post vou te mostrar a nova classe Optional do Java 8, que irá te ajudar em dois pontos: evitar a famosa exceção NullPointerException e deixar seu código mais bonito e limpo.

Como sempre gosto de fazer, vou criar um exemplo que simula algo na vida real, que você pode encontrar no seu dia a dia como programador, mesmo sendo bem simples.

O sistema é um cadastro de motoristas de caminhões para uma empresa de logística. Cada motorista pode ter o seu caminhão próprio que pode ou não ter um seguro.

Repare que eu disse pode ter o seu caminhão próprio, ou seja, não é obrigatório ter um, o motorista pode alugar ou simplesmente ser terceirizado. O seguro também é opcional nesse caso.

Veja o diagrama de classes desse exemplo abaixo:

Diagrama de classe

Vamos pensar uma tela de consulta desse sistema, o usuário informa o nome do motorista para recuperar a cobertura do caminhão. Isso seria importante por exemplo, para decidirmos se determinada carga será entregue pelo motorista A, B ou C.

Vamos implementar do modo pré Java 8 para conseguirmos enxergar as mudanças aos poucos. Segue abaixo a implementação das classes Motorista, Caminhao e Seguro:

public class Motorista {

    private String nome;
    private Integer idade;
    private Caminhao caminhao;

    // construtor, getters e setters

}
public class Caminhao {

    private String modelo;
    private Seguro seguro;

    // construtor, getters e setters

}
public class Seguro {

    private String cobertura;
    private BigDecimal valorFranquia;

    // construtor, getters e setters

}

Para não dificultar o exemplo, a consulta do motorista será feita em uma classe Motoristas, que faz a busca em um Map inicializado na construção do objeto. Vamos determinar que não existem dois motoristas com o mesmo nome.

public class Motoristas {

    private Map<String, Motorista> motoristas = new HashMap<>();
    
    public Motoristas() {
        Seguro seguro = new Seguro("Parcial - não cobre roubo", new BigDecimal("5000"));
        Caminhao caminhao = new Caminhao("Mercedes Atron", seguro);
        Motorista motoristaJoao = new Motorista("João", 40, caminhao);
        
        Motorista motoristaJose = new Motorista("José", 25, null); // Não tem caminhão
        
        motoristas.put("João", motoristaJoao);
        motoristas.put("José", motoristaJose);
    }
    
    public Motorista porNome(String nome) {
        return motoristas.get(nome);
    }
  
}

Agora, vamos ao código que poderíamos usar para imprimir a cobertura de um caminhão que pertence a um motorista:

Motorista motorista = motoristas.porNome("João");
String cobertura = motorista.getCaminhao().getSeguro().getCobertura();
cobertura = cobertura != null ? cobertura : "Sem seguro";

Tenho certeza que quando você leu esse código pensou: “Xiiii… isso pode dar um NullPointerException horrível na tela!

Então, vamos resolver:

Motorista motorista = motoristas.porNome("João");
String cobertura = "Sem seguro";

if (motorista != null) {
    Caminhao caminhao = motorista.getCaminhao();
    if (caminhao != null) {
        Seguro seguro = caminhao.getSeguro();
        if (seguro != null) {
            cobertura = seguro.getCobertura();
        }
    }
}

Isso é o que chamamos de código verboso, ou seja, muita coisa escrita para fazer pouca coisa! Além de ser chato de ler.

Ao final do artigo, você irá aprender a reduzir esse código para:

String cobertura = motoristas.porNome("João")
  .flatMap(Motorista::getCaminhao)
  .flatMap(Caminhao::getSeguro)
  .map(Seguro::getCobertura)
  .orElse("Sem seguro");

Ficou animado? :) Então aguente firme, deixa eu começar te explicando alguns fundamentos do que é o Optional e, logo logo chegaremos a um código simples e bonito, que você pode usar para impressionar sua(seu) namorada(o).

Em primeiro lugar, é importante dizer que o Optional não veio para substituir o null no Java, mas sim ajudá-lo a pensar o que fazer quando o valor não está presente, consequentemente evitando o famoso e feio NullPointerException.

Você pode pensar no Optional como sendo uma caixa que pode ou não ter um valor dentro, se não tiver nada, dizemos que a caixa está vazia.

Para criar um objeto do tipo Optional, que fica no pacote java.util, podemos usar o método estático of, por exemplo:

Seguro seguro = new Seguro("Total com franquia reduzida", new BigDecimal("600"));
Optional seguroOpcional = Optional.of(seguro);

Repare que o Optional usa o generics para determinar o tipo de objeto que ele está guardando, no caso acima, Seguro.

Antes de aprendermos a mapear um Optional, deixa eu só te contar que, se você tentar passar null para o método of, será lançado uma NullPointerException. Então, se existir a possibilidade da criação de um Optional vazio, utilize o método ofNullable, veja abaixo:

Seguro seguro = null;
Optional seguroOpcional = Optional.ofNullable(seguro);

Pense na seguinte situação: queremos imprimir na tela o valor da franquia, mas caso o seguro não exista, simplesmente não iremos fazer nada.

Repare que, com um código bem simples de entender conseguimos fazer isso, veja abaixo:

seguroOpcional.map(Seguro::getValorFranquia).ifPresent(System.out::println);

Deixa eu te contar como você pode ler esse código em português: no objeto seguroOpcional, pegue o valor da franquia e, caso exista, imprima na tela por favor.

Tecnicamente o que ocorre é: o método map retorna um novo Optional de BigDecimal, pois usamos o Seguro::getValorFranquia no mapeamento.

E o método ifPresent será executado se existir alguma coisa no Optional, neste caso ele chama o System.out::println passando o valor da franquia.

Então agora pense comigo, se o Seguro é opcional no Caminhao e este é opcional no Motorista, o que acha de alterarmos estes atributos em cada classe para um Optional? Veja como poderia ficar:

public class Caminhao {

  private String modelo;
  private Optional seguro;

  public Caminhao(String modelo, Optional seguro) {
    this.modelo = modelo;
    this.seguro = seguro;
  }

        // getters e setters
}
public class Motorista {

  private String nome;
  private Integer idade;
  private Optional caminhao;

  public Motorista(String nome, Integer idade, Optional caminhao) {
    this.nome = nome;
    this.idade = idade;
    this.caminhao = caminhao;
  }
      
        // getters e setters
}

A classe de consulta seria alterada para:

public class Motoristas {
  
  private Map<String, Optional> motoristas = new HashMap<>();
  
  public Motoristas() {
    Seguro seguro = new Seguro("Parcial - não cobre roubo", new BigDecimal("5000"));
    Caminhao caminhao = new Caminhao("Mercedes Atron", Optional.ofNullable(seguro));
    Optional motoristaJoao = Optional.of(new Motorista("João", 40, Optional.ofNullable(caminhao)));
    Optional motoristaJose = Optional.of(new Motorista("José", 25, Optional.ofNullable(null)));
    
    motoristas.put("João", motoristaJoao);
    motoristas.put("José", motoristaJose);
  }

  public Optional porNome(String nome) {
    return motoristas.get(nome);
  }
  
}

Já te falei que o método map retorna um outro Optional, certo? Então você poderia pensar, basta eu encadear várias chamadas desse método, mas… veja só, o código abaixo não compila:

Optional caminhaoOpcional = motoristas.porNome("João")
      .map(Motorista::getCaminhao);

Sabe por quê? Porque como o atributo em Motorista é do tipo Optional<Caminhao>, ele irá retornar um Optional<Optional<Caminhao>>.

Pensando nisso que eles criaram o método flatMap, que podemos traduzir como “achate o Optional“, ou seja, ao invés de optional de optional de caminhão, será devolvido um optional de caminhão. Veja abaixo, o código agora está certo, compila sem problemas:

Optional caminhaoOpcional = motoristas.porNome("João")
      .flatMap(Motorista::getCaminhao);

Os caras pensam em tudo mesmo, não é verdade!? hehe

Vamos encadear a chamada dos métodos até chegarmos ao último Optional e chamar orElse que já te explico o que faz.

String cobertura = motoristas.porNome("João")
      .flatMap(Motorista::getCaminhao)
      .flatMap(Caminhao::getSeguro)
      .map(Seguro::getCobertura)
      .orElse("Sem seguro");

Repare que usamos o map em map(Seguro::getCobertura), pois nesse caso, o atributo cobertura de Seguro já é uma String direta, e não um Optional.

Você está se perguntando, o que é esse orElse ai?

É outro método bem interessante do Optional. Leia da seguinte forma: se o Optional tiver algum resultado, retorne, caso contrário retorne esse valor aqui.

No caso do código acima, caso o motorista não tenha caminhão, ou não tenha seguro, será retornado a String “Sem seguro”.

Para onde foram aquelas quantidades de ifs que usávamos? Sumiram!!! Nosso código ficou bem mais simples e fácil de ler, basta você se acostumar com a API. ;)

Assista ao vídeo com a explicação passo a passo do exemplo, que você vai conseguir entender muito melhor.

Você já conhecia o Optional? Comente sobre o que achou da vídeo aula e artigo. :)

Graduado em Engenharia Elétrica pela Universidade Federal de Uberlândia e detentor das certificações LPIC-1, SCJP e SCWCD.

Olá,

o que você achou deste conteúdo? Conte nos comentários.

Junte-se a mais de 100.000 pessoas

Entre para nossa lista e receba conteúdos exclusivos e com prioridade

Você se Inscreveu com Sucesso!