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!

Blog da AlgaWorks
Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.