evite a verificação da Declaração nula em Java

visão geral

geralmente, variáveis nulas, referências e coleções são difíceis de lidar no código Java. Não só são difíceis de identificar, como também são complexos de lidar.

Na verdade, qualquer falha em lidar com null não pode ser identificada no tempo de compilação e resulta em uma percepção NULL no tempo de execução.

neste tutorial, vamos dar uma olhada na necessidade de verificar se null em Java e várias alternativas que nos ajudam a evitar verificações nulas em nosso código.

Further reading:

Using NullAway to Avoid NullPointerExceptions

Learn how to avoid NullPointerExceptions using NullAway.
Read more →

Spring Null-Safety Annotations

A quick and practical guide to null-safety annotations in Spring.
Read more →

Introduction to the Null Object Pattern

Learn about the Null Object Pattern and how to implement it in Java
Read more →

What Is NullPointerException?

de Acordo com o Javadoc para NullPointerException, que é acionada quando um aplicativo tenta usar null em um caso onde um objeto é necessário, tais como:

  • Chamar um método de instância de um objeto nulo
  • Acesso ou a modificação de um campo de um objeto nulo
  • , Tendo o comprimento nulo, como se fosse uma matriz
  • Acessar ou modificar os slots de nulo, como se fosse uma matriz
  • Jogando nulo, como se fosse algum tipo de bebida valor

Vamos ver rapidamente alguns exemplos de código Java que causa essa exceção:

public void doSomething() { String result = doSomethingElse(); if (result.equalsIgnoreCase("Success")) // success }}private String doSomethingElse() { return null;}

aqui, tentamos invocar uma chamada de método para uma referência nula. Isso resultaria em um ponto final.

outro exemplo comum é se tentarmos aceder a uma matriz nula:

public static void main(String args) { findMax(null);}private static void findMax(int arr) { int max = arr; //check other elements in loop}

isto causa uma percepção de ponto NULL na linha 6.

assim, acessar qualquer campo, método ou índice de um objeto nulo causa uma percepção de ponto null, Como pode ser visto a partir dos exemplos acima.

uma forma comum de evitar a solução de NullPointerException é verificar se null:

public void doSomething() { String result = doSomethingElse(); if (result != null && result.equalsIgnoreCase("Success")) { // success } else // failure}private String doSomethingElse() { return null;}

No mundo real, os programadores acham difícil identificar quais objetos podem ser nulos. Uma estratégia agressivamente segura poderia ser verificar null para cada objeto. Isto, no entanto, causa um monte de verificações nulas redundantes e torna o nosso Código menos legível.

nas próximas seções, vamos passar por algumas das alternativas em Java que evitam tal redundância.como discutido na última seção, o acesso a métodos ou variáveis de objetos nulos causa uma percepção de ponto null. Também discutimos que colocar uma verificação nula em um objeto antes de acessá-lo elimina a possibilidade de Nullpointerexcepção.

no entanto, muitas vezes existem APIs que podem lidar com valores nulos. Por exemplo:

public void print(Object param) { System.out.println("Printing " + param);}public Object process() throws Exception { Object result = doSomething(); if (result == null) { throw new Exception("Processing fail. Got a null response"); } else { return result; }}

The print() method call would just print” null ” but won’t throw an exception. Da mesma forma, o processo() nunca retornaria nulo em sua resposta. Abre uma excepção.

assim, para um código cliente acessando a APIs acima, não há necessidade de uma verificação nula.no entanto, tais APIs devem explicitar no seu contrato. Um lugar comum para APIs publicar tal contrato é o JavaDoc.

isto, no entanto, não dá nenhuma indicação clara do contrato API e, portanto, depende dos desenvolvedores de código cliente para garantir a sua conformidade.

na próxima seção, veremos como alguns IDEs e outras ferramentas de desenvolvimento ajudam os desenvolvedores com isso.automatizar contratos de API

4.1. Usando a análise de código estático

Ferramentas de análise de código estático ajudam a melhorar a qualidade do código para uma grande quantidade. E algumas dessas ferramentas também permitem que os desenvolvedores mantenham o contrato nulo. Um exemplo é o FindBugs.

FindBugs ajuda a gerir o contrato nulo através das anotações @Nullable e @NonNull. Podemos usar estas anotações sobre qualquer método, campo, variável local ou parâmetro. Isto torna explícito ao código do cliente se o tipo anotado pode ser nulo ou não. Vejamos um exemplo:

public void accept(@Nonnull Object param) { System.out.println(param.toString());}

Aqui, @NonNull deixa claro que o argumento não pode ser nulo. Se o código cliente chamar este método sem verificar o argumento de nulo, FindBugs geraria um aviso no tempo de compilação.

4.2. Usando o suporte IDE

os desenvolvedores geralmente dependem de IDEs para escrever código Java. E características como a conclusão inteligente do código e avisos úteis, como quando uma variável pode não ter sido atribuída, certamente ajudam em grande medida.

alguns IDEs também permitem que os desenvolvedores gerenciem contratos de API e, assim, eliminar a necessidade de uma ferramenta de análise de código estático. A ideia do intelij fornece as anotações @NonNull e @Nullable. Para adicionar o suporte para estas anotações no Intelij, devemos adicionar a seguinte dependência Maven:

<dependency> <groupId>org.jetbrains</groupId> <artifactId>annotations</artifactId> <version>16.0.2</version></dependency>

Agora, o Intelij irá gerar um aviso se faltar a verificação nula, como no nosso último exemplo.

Intelij também fornece uma anotação de contrato para o tratamento de contratos de API complexos.

5. Assertions

até agora, nós só falamos sobre remover a necessidade de verificações nulas do Código do cliente. Mas isso raramente é aplicável em aplicações do mundo real.

agora, vamos supor que estamos trabalhando com uma API que não pode aceitar parâmetros nulos ou pode retornar uma resposta nula que deve ser tratada pelo cliente. Isto apresenta a necessidade de verificar os parâmetros ou a resposta para um valor nulo.

Aqui, podemos usar afirmações Java em vez da declaração condicional de verificação nula tradicional:

public void accept(Object param){ assert param != null; doSomething(param);}

na linha 2, verificamos para um parâmetro nulo. Se as asserções estão ativadas, isso resultaria em um erro de asserção.apesar de ser uma boa maneira de afirmar condições prévias como parâmetros não nulos, esta abordagem tem dois problemas principais. :

  1. asserções são geralmente desativadas em uma JVM
  2. uma falsa asserção resulta em um erro Não controlado que é irrecuperável

portanto, não é recomendado para programadores a usar asserções para verificar as condições. Nas secções seguintes, discutiremos outras formas de lidar com validações nulas.

evitar verificações nulas através de práticas de codificação

6.1. Pré-condições

geralmente é uma boa prática escrever código que falha cedo. Portanto, se uma API aceita vários parâmetros que não estão autorizados a ser nulos, é melhor verificar para cada parâmetro não null como uma condição prévia da API.

Por exemplo, vamos olhar para dois métodos-um que falha cedo, e um que não:

public void goodAccept(String one, String two, String three) { if (one == null || two == null || three == null) { throw new IllegalArgumentException(); } process(one); process(two); process(three);}public void badAccept(String one, String two, String three) { if (one == null) { throw new IllegalArgumentException(); } else { process(one); } if (two == null) { throw new IllegalArgumentException(); } else { process(two); } if (three == null) { throw new IllegalArgumentException(); } else { process(three); }}

claramente, devemos preferir goodacept() sobre badacept().

Como alternativa, também podemos usar as pré-condições da Goiaba para validar parâmetros da API.

6, 2. Usando primitivos em vez de classes de espectros

Uma vez que null não é um valor aceitável para primitivos como int, nós devemos preferi-los sobre seus homólogos wrapper como inteiro sempre que possível.

Considere duas implementações de um método que soma dois números inteiros:

public static int primitiveSum(int a, int b) { return a + b;}public static Integer wrapperSum(Integer a, Integer b) { return a + b;}

Agora, vamos chamar essas APIs em nosso código de cliente:

int sum = primitiveSum(null, 2);

Isso resultaria em um erro de tempo de compilação como um valor null não é um valor válido para um int.

E ao usar a API com classes de wrap, temos uma percepção de NullPointerException:

assertThrows(NullPointerException.class, () -> wrapperSum(null, 2));

Existem também outros fatores para o uso de primitivos sobre embalagens, como nós cobrimos em outro tutorial, Java Primitives versus objetos.

6. 3. Coleções vazias

ocasionalmente, precisamos retornar uma coleção como uma resposta de um método. Por tais métodos, devemos sempre tentar retornar uma coleção vazia em vez de null:

public List<String> names() { if (userExists()) { return Stream.of(readName()).collect(Collectors.toList()); } else { return Collections.emptyList(); }}

por isso, tenho evitado a necessidade de nosso cliente para executar uma verificação de null ao chamar este método.

usando objetos

Java 7 introduziu a nova API de objetos. Esta API tem vários métodos utilitários estáticos que tiram um monte de código redundante. Vejamos um tal método, requireNonNull():

public void accept(Object param) { Objects.requireNonNull(param); // doSomething()}

Agora, vamos testar a aceitar() método:

assertThrows(NullPointerException.class, () -> accept(null));

Então, se for null é passado como um argumento, accept() lança um NullPointerException.

esta classe também tem métodos isNull() e nonNull() que podem ser usados como predicados para verificar um objeto para null.

usando opcional

8.1. Usando orElseThrow

Java 8 introduziu uma nova API opcional na linguagem. Isto oferece um melhor contrato para lidar com valores opcionais em comparação com null. Vamos ver como opcional retira a necessidade de verificações nulas:

public Optional<Object> process(boolean processed) { String response = doSomething(processed); if (response == null) { return Optional.empty(); } return Optional.of(response);}private String doSomething(boolean processed) { if (processed) { return "passed"; } else { return null; }}

devolvendo uma Opcional, Como mostrado acima, o método de processo deixa claro para o chamador que a resposta pode ser vazia e deve ser tratada no tempo de compilação.

isto elimina notavelmente a necessidade de quaisquer verificações nulas no código do cliente. Uma resposta vazia pode ser tratada de forma diferente usando o estilo declarativo da API opcional:

assertThrows(Exception.class, () -> process(false).orElseThrow(() -> new Exception()));

além disso, ele também fornece um melhor contrato para desenvolvedores API para significar aos clientes que uma API pode retornar uma resposta vazia.

apesar de eliminarmos a necessidade de uma verificação nula sobre o chamador desta API, nós a usamos para retornar uma resposta vazia. Para evitar isso, opcional fornece um método ofNullable que devolve um opcional com o valor especificado, ou vazio, se o valor for nulo:

public Optional<Object> process(boolean processed) { String response = doSomething(processed); return Optional.ofNullable(response);}

8.2. Usando opcional com colecções

ao lidar com colecções vazias, o opcional vem à mão:

public String findFirst() { return getList().stream() .findFirst() .orElse(DEFAULT_VALUE);}

Esta função deve devolver o primeiro item de uma lista. A função findFirst da API Stream irá retornar uma opção vazia quando não houver dados. Aqui, nós usamos orElse para fornecer um valor padrão em vez disso.

Isto permite-nos lidar com listas vazias, ou listas, que depois de termos usado o método de filtragem da biblioteca de fluxo, não têm itens a fornecer.

alternativamente, também podemos permitir que o cliente decida Como lidar com o vazio, devolvendo opcional deste método:

portanto, se o resultado do getList estiver vazio, este método irá devolver um opcional vazio ao cliente.

Usando opcional com coleções nos permite projetar APIs que certamente retornarão valores não nulos, evitando assim verificações nulas explícitas no cliente.

é importante notar aqui que esta implementação depende do getList não retornar nulo. No entanto, como discutimos na última seção, muitas vezes é melhor devolver uma lista vazia em vez de uma nula.

8, 3. Combinando opcionais

quando começamos a fazer com que as nossas funções retornem opcionais precisamos de uma forma de combinar os seus resultados em um único valor. Vamos pegar nosso exemplo de getList de antes. E se ele retornasse uma lista opcional, ou fosse embrulhado com um método que envolvesse um null com opcional usando ofNullable?

o Nosso método findFirst quer voltar Opcional primeiro elemento de uma lista Opcional:

public Optional<String> optionalListFirst() { return getOptionalList() .flatMap(list -> list.stream().findFirst());}

usando a flatMap função Opcional retornado de getOptional nós pode descompactar o resultado de um interior expressão que retorna Opcional. Sem flatMap, o resultado seria Opcional<Opcional<String>>. A operação flatMap só é realizada quando o opcional não está vazio.

bibliotecas

9.1. O uso de Lombok

Lombok é uma grande biblioteca que reduz a quantidade de código de boilerplate em nossos projetos. Ele vem com um conjunto de anotações que tomam o lugar de Partes Comuns do código que muitas vezes escrevemos em aplicações Java, tais como getters, setters, e toString(), para citar alguns.

outra das suas anotações é @NonNull. Então, se um projeto já usa Lombok para eliminar o código boilerplate, @NonNull pode substituir a necessidade de verificações nulas.

Antes de passarmos para ver alguns exemplos, vamos adicionar um Maven dependency para Lombok:

<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.6</version></dependency>

Agora, podemos usar @não nula sempre que um valor nulo de seleção é necessário:

public void accept(@NonNull Object param){ System.out.println(param);}

Então, nós simplesmente anotado o objeto para o qual o nulo de seleção já teria sido necessário, e Lombok gera compilado classe:

public void accept(@NonNull Object param) { if (param == null) { throw new NullPointerException("param"); } else { System.out.println(param); }}

Se param é nulo, este método lança uma NullPointerException. O método deve tornar isso explícito em seu contrato, e o código do cliente deve lidar com a exceção.

9, 2. Usando StringUtils

geralmente, a validação de String inclui uma verificação para um valor vazio, além do valor nulo. Portanto, uma declaração de validação comum seria:

public void accept(String param){ if (null != param && !param.isEmpty()) System.out.println(param);}

isto rapidamente se torna redundante se tivermos que lidar com um monte de tipos de cadeia. É aqui que o Stingutils dá jeito. Antes de ver isso em ação, vamos adicionar uma dependência Maven para commons-lang3:

<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.11</version></dependency>

vamos agora refinar o código acima com Stingutils:

public void accept(String param) { if (StringUtils.isNotEmpty(param)) System.out.println(param);}

assim, substituímos a nossa verificação nula ou vazia por um método utilitário estático isNotEmpty(). Esta API oferece outros métodos utilitários poderosos para lidar com funções de cadeia de caracteres comuns.

conclusão

neste artigo, olhamos para as várias razões para a percepção de ponto Null e por que é difícil de identificar. Em seguida, vimos várias maneiras de evitar a redundância em código em torno da verificação de null com parâmetros, tipos de retorno, e outras variáveis.

Todos os exemplos estão disponíveis no GitHub.

Começar com Mola 5 e Primavera de Inicialização 2, através do Aprender a Primavera curso:

>> confira O CURSO



Deixe uma resposta

O seu endereço de email não será publicado.