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:
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. :)
Olá,
o que você achou deste conteúdo? Conte nos comentários.