Evite o NullPointerException no Java
Talvez o erro mais comum que os desenvolvedores enfrentam, principalmente quando estão dando os primeiros passos no universo da programação, é o famoso NullPointerException (NPE para os íntimos).
Entretanto, com alguns cuidados, que podemos chamar de programação defensiva, conseguimos nos prevenir de tomar essa exceção.
Para nos ajudar a entender melhor como prevenir um NPE, vamos tomar como exemplo uma aplicação que realiza pagamentos. Nela, temos uma classe que é a responsável por salvar o cartão de crédito do usuário:
public class CartaoDeCredito {
private String titular;
private String numero;
private String cvv;
private LocalDate dataDeExpiracao;
//getters e setters
}
Também temos uma classe que representa o usuário desse sistema:
public class Usuario {
private String nomeCompleto;
private String cpf;
private List<CartaoDeCredito> cartoes;
//getters e setters
}
Vocês acreditariam se eu dissesse que este código já está propenso a receber um NPE? Antes de explicar o porquê, vamos entender melhor essa exceção.
NullPointerException
NullPointerException
é uma exceção que indica que a aplicação tentou usar uma referência de um objeto que estava com valor nulo.
Extende a classe RuntimeException, que, por sua vez, engloba exceções que são disparadas em tempo de execução. Além disso, NPE é uma unchecked exception, logo, ela não precisa ser tratada e o compilador não acusa erro em tempo de compilação.
As unchecked exceptions representam falhas no código feitas pelo programador, (sim, a culpa é nossa). Normalmente são bugs da aplicação que poderiam ter sido evitados caso o desenvolvedor tomasse mais cuidado na hora de programar.
Mas por que tomamos NPE’s? Quais as causas? O que deixa o código propenso a essa exceção?
Se fossemos listar as ações que causam NullPointerException
teríamos algo assim:
- Chamar um método de uma referência de objeto que esteja com valor nulo
- Acessar ou modificar um atributo de uma referência de objeto nula
- Em caso de array ou coleções, usar métodos de elementos nulos
- Lançar um
NullPointerException
- Fazer uso de unboxing em um objeto de valor nulo
- Tentar pegar o tamanho de uma array nula
Voltando ao código mostrado no começo, vou provar para vocês, que aquele código já está muito propenso a receber um NPE.
Bom, temos um método que, dado um novo cartão que o usuário está tentando cadastrar, verifica pelo número se aquele cartão já consta cadastrado na lista de cartões do usuário. Podemos ver o código do método logo abaixo:
public static boolean cartaoExistente(CartaoDeCredito cartaoDeCredito, Usuario usuario){
for (CartaoDeCredito cartao : usuario.getCartoes()) {
if(cartaoDeCredito.getNumero().equals(cartao.getNumero())){
return false;
}
}
return true;
}
Para mostrar para vocês esse método funcionando, vou criar um usuário novo e tentar validar um novo cartão de crédito.
Como acabamos de criar o usuário, em teoria o resultado deveria ser true
pois não temos nenhum cartão cadastrado ainda. Para o teste, iremos usar o seguinte código:
public static void main(String[] args) {
CartaoDeCredito cartaoDeCredito = new CartaoDeCredito("Mário E Alvial", "5152877727714129", "317", LocalDate.of(2019, 6, 9));
Usuario usuario = new Usuario("Mário Sérgio Esteves Alvial", "75475962820");
System.out.println(cartaoExistente(cartaoDeCredito, usuario));
}
Ao executar este código, temos o seguinte resultado:
Tem mais coisas do que eu estava esperando. Tomamos um NPE, mas por que? Eu preenchi todas as informações, tanto do cartão quanto do usuário, o que está nulo?
Lembra que eu falei que só aquela declaração de classe já deixava aberta a possibilidade de NPE? Então, foi ela a causa do problema. Todos os atributos daquelas classes foram declarados, mas não foram inicializados, logo, por não serem de tipos primitivos, o valor padrão desses atributos é nulo.
Então quando fomos iterar a lista de cartões do usuário para ver se já existia algum cartão com aquele número, na verdade, iteramos em uma lista nula e foi aí que tomamos o NPE.
Para consertar esse código podemos encapsular nosso for
dentro de um if
que verifica se a lista de cartões não está nula. Algo assim:
public static boolean cartaoExistente(CartaoDeCredito cartaoDeCredito, Usuario usuario) {
if (usuario.getCartoes() != null) {
for (CartaoDeCredito cartao : usuario.getCartoes()) {
if (cartaoDeCredito.getNumero().equals(cartao.getNumero())) {
return false;
}
}
}
return true;
}
Problema de lista nula resolvido. Mas imagina ter que ficar fazendo esse if
toda vez que tiver a chance de algo estar nulo? Além do trabalho, o código ficará extremamente deselegante. Então vamos tentar cortar o mal pela raiz de uma vez por todas.
Codificando Defensivamente
Uma ótima prática no mundo da programação que diminui e muito as chances de tomar NPE é inicializar os atributos da sua classe. Pois assim, o valor padrão deles nunca será nulo. No nosso exemplo, podemos fazer assim:
public class CartaoDeCredito {
private String titular = "";
private String numero = "";
private String cvv = "";
private LocalDate dataDeExpiracao = LocalDate.now();
}
public class Usuario {
private String nomeCompleto = "";
private String cpf = "";
private List<CartaoDeCredito> cartoes = new ArrayList<>();
}
Pronto, inicializados. Com isso, podemos tirar aquele if
que verificava se a lista de cartões do usuário estava nula, pois sabemos que ela não está.
Outro ponto importante é evitar ao máximo, eu até diria não fazer, definitivamente não fazer, atribuir nulo para alguma variável.
Eu entendo que às vezes você quer ter uma referência nula para atribuir valor para ela posteriormente, por exemplo, agora o processo de criação de usuário ficou um pouco mais complexo, precisamos saber se o usuário é homem ou mulher, pois o processo de criação de ambos são diferentes, para isso temos este código:
public static Usuario criaUsuario(boolean isMulher){
Usuario usuario = null;
if(isMulher){
usuario = criaUsuarioMulher();
}else{
usuario = criaUsuarioHomem();
}
return usuario;
}
Este código, por si só, não lança um NPE, mas nos deixa em alerta, pois qualquer método que chamarmos usando a referência usuario
com valor nulo, irá disparar um NPE. Uma forma de evitar isso é fazendo um early return dessa forma:
public static Usuario criaUsuario(boolean isMulher){
if(isMulher) {
return criaUsuarioMulher();
}
return criaUsuarioHomem()
}
O código fica até mais simples não acham? Meu ponto é, realmente não atribua valor nulo para nada.
Sempre valide dados "não confiáveis". Podemos atribuir esse nome a dados vindos de fontes externas da sua aplicação, seja vindo do usuário ou de uma aplicação externa. Eu chamo de dados “não confiáveis” pois você não sabe o que tem neles, não foi você que enviou esses dados, não tem como saber.
Então faça validações, seja usando anotações, seja realmente validando para ver se os campos recebimos não estão nulos ou em formato errado, não importa. O importante é validar os dados desconhecidos.
E por último, mas não menos importante, tente ao máximo usar métodos referentes a objetos que você tem certeza que não estão nulos.
Por exemplo, voltando ao nosso método que verifica se o cartão já existe na lista de cartões de usuário:
public static boolean cartaoExistente(CartaoDeCredito cartaoDeCredito, Usuario usuario){
for (CartaoDeCredito cartao : usuario.getCartoes()) {
if(cartaoDeCredito.getNumero().equals(cartao.getNumero())){
return false;
}
}
return true;
}
Vamos pensar que apenas inicializamos o atributo que representa a lista de cartões de usuário, o resto ainda está nulo. Também, já temos cartões válidos salvos na nossa lista.
Agora, concordam comigo que caso o usuário não preencha o número do cartão dele tomaremos um NPE nesta parte do código do nosso método:
if (cartaoDeCredito.getNumero().equals(cartao.getNumero())) {
return false;
}
Pois o objeto cartaoDeCredito
está chamando o método getNumero()
que devolve o valor do atributo numero
que, no caso, está nulo. Portanto, quem está chamando o método equals()
está nulo e, por sua vez, iremos receber um NPE.
Para evitar isso podemos validar as informações vindas de fonte externa. Beleza, podemos, mas também podemos ao invés de chamar o equals()
pelo objeto preenchido pelo usuário, chamá-lo pelo cartão iterado pelo for
. Dessa forma:
if (cartao.getNumero().equals(cartaoDeCredito.getNumero())) {
return false;
}
Pronto, não tomaremos mais NPE pelo motivo que estávamos tomando, pois, sabemos que o atributo numero
dos cartões que já estão na lista do usuário estão preenchidos, logo, o método equals()
não será chamado por um objeto nulo.
A classe Optional
Com o Java 8 veio uma nova classe chamada Optional, que por sua vez, traz um conjunto de métodos para tratar blocos de código críticos. O Optional
pode ser pensado como algo que pode ou não ter valor. Se não tiver, dizemos que ele está vazio.
Para explicar essa classe de forma consistente, eu teria que escrever um post completo, pois o assunto não é pequeno. Existem diversas vantagens no bom uso de Optional dentre elas a proteção contra NPE, mas também, é mais difícil entender seu uso logo de primeira, portanto um estudo aprofundado dessa classe é necessário.
Para aprender mais sobre exceções, inclusive NPE, a Alura possui o curso Curso Java II: Orientação a Objetos. E, também, se você ficou interessado pelo Optional, tem o Curso Java 8: Tire proveito dos novos recursos da linguagem, que além de falar sobre esta classe, também mostra outras funcionalidades que o Java 8 trouxe.