Java: Como verificar se há pointers nulos com eficiência

Existem alguns padrões para verificar se um parâmetro para um método recebeu um valor null .

Primeiro, o clássico. É comum em código próprio e óbvio para entender.

 public void method1(String arg) { if (arg == null) { throw new NullPointerException("arg"); } } 

Em segundo lugar, você pode usar um framework existente. Esse código parece um pouco melhor porque ocupa apenas uma única linha. A desvantagem é que ele potencialmente chama outro método, o que pode tornar o código mais lento, dependendo do compilador.

 public void method2(String arg) { Assert.notNull(arg, "arg"); } 

Terceiro, você pode tentar chamar um método sem efeitos colaterais no object. Isso pode parecer estranho no início, mas tem menos tokens do que as versões acima.

 public void method3(String arg) { arg.getClass(); } 

Eu não vi o terceiro padrão em uso amplo, e é quase como se eu tivesse inventado isso sozinho. Eu gosto disso por sua falta, e porque o compilador tem uma boa chance de otimizá-lo completamente ou convertê-lo em uma única instrução de máquina. Eu também compilo meu código com informação de número de linha, então se um NullPointerException é lançado, eu posso rastreá-lo de volta para a variável exata, já que eu tenho apenas uma verificação por linha.

Qual cheque você prefere e por quê?

Abordagem # 3: arg.getClass(); é inteligente, mas, a menos que essa expressão tenha uma adoção generalizada, prefiro os methods mais claros e detalhados, em vez de salvar alguns caracteres. Eu sou um tipo de programador “escreva uma vez, leia muitos”.

As outras abordagens são auto-documentadas: há uma mensagem de log que você pode usar para esclarecer o que aconteceu – essa mensagem de log é usada ao ler o código e também em tempo de execução. arg.getClass() , como está, não é auto-documentado. Você poderia usar um comentário pelo menos o esclarecer aos revisores do código:

 arg.getClass(); // null check 

Mas você ainda não tem a chance de colocar uma mensagem específica no tempo de execução como você pode com os outros methods.


Abordagem # 1 vs # 2 (null-check + NPE / IAE vs assert): Eu tento seguir as diretrizes como esta:

http://data.opengeo.org/GEOT-290810-1755-708.pdf

  • Use assert para verificar parâmetros em methods privados
    assert param > 0;

  • Use a verificação nula + IllegalArgumentException para verificar parâmetros em methods públicos
    if (param == null) throw new IllegalArgumentException("param cannot be null");

  • Use a verificação nula + NullPointerException onde necessário
    if (getChild() == null) throw new NullPointerException("node must have children");


No entanto , como essa é uma questão relacionada à captura de possíveis problemas null com mais eficiência, devo mencionar que meu método preferido para lidar com null é usar análise estática, por exemplo, annotations de tipo (por exemplo, @NonNull ) à la JSR-305 . Minha ferramenta favorita para verificá-los é:

A estrutura do verificador:
Tipos conectáveis ​​personalizados para Java
https://checkerframework.org/manual/#checker-guarantees

Se for o meu projeto (por exemplo, não uma biblioteca com uma API pública) e se eu puder usar a Estrutura do verificador durante todo o processo:

  • Eu posso documentar minha intenção mais claramente na API (por exemplo, esse parâmetro pode não ser nulo (o padrão), mas este pode ser nulo ( @Nullable ; o método pode retornar null; etc). Essa anotação está correta na declaração, em vez de mais longe no Javadoc, é muito mais provável que seja mantida.

  • análise estática é mais eficiente que qualquer verificação de tempo de execução

  • A análise estática sinalizará possíveis falhas de lógica antecipadamente (por exemplo, tentei transmitir uma variável que pode ser nula para um método que aceita apenas um parâmetro não nulo) em vez de depender do problema que ocorre no tempo de execução.

Um outro bônus é que a ferramenta me permite colocar as annotations em um comentário (por exemplo, `/ @Nullable /), então meu código de biblioteca pode ser compatível com projetos anotados por tipo e projetos sem anotação de tipo (não que eu tenha algum desses ).


Caso o link fique inoperante novamente , aqui está a seção do Guia do desenvolvedor do GeoTools:

http://data.opengeo.org/GEOT-290810-1755-708.pdf

5.1.7 Uso de asserções, IllegalArgumentException e NPE

A linguagem Java tem, há alguns anos, uma palavra-chave declarada disponível; essa palavra-chave pode ser usada para executar verificações somente de debugging. Embora existam vários usos desse recurso, um comum é verificar os parâmetros do método em methods privados (não públicos). Outros usos são pós-condições e invariantes.

Referência: Programação com afirmações

Pré-condições (como verificações de argumentos em methods privados) são geralmente alvos fáceis para asserções. Pós-condições e invariantes são por vezes menos fáceis, mas mais valiosas, uma vez que condições não-triviais têm mais riscos a serem quebrados.

  • Exemplo 1: Após uma projeção de mapa no módulo de referência, uma declaração executa a projeção do mapa inverso e verifica o resultado com o ponto original (pós-condição).
  • Exemplo 2: Em implementações DirectPosition.equals (Object), se o resultado for true, a asserção garante que hashCode () seja idêntico ao exigido pelo contrato Object.

Use Assert para verificar Parameters on Private methods

 private double scale( int scaleDenominator ){ assert scaleDenominator > 0; return 1 / (double) scaleDenominator; } 

Você pode ativar asserções com o seguinte parâmetro de linha de comando:

 java -ea MyApp 

Você pode ativar apenas asserções do GeoTools com o seguinte parâmetro de linha de comando:

 java -ea:org.geotools MyApp 

Você pode desativar as asserções para um pacote específico, como mostrado aqui:

 java -ea:org.geotools -da:org.geotools.referencing MyApp 

Use IllegalArgumentExceptions para verificar parâmetros em methods públicos

O uso de declarações em methods públicos é estritamente desencorajado; porque o erro relatado foi feito no código do cliente – seja honesto e diga a eles com um IllegalArgumentException quando tiverem errado.

 public double toScale( int scaleDenominator ){ if( scaleDenominator > 0 ){ throw new IllegalArgumentException( "scaleDenominator must be greater than 0"); } return 1 / (double) scaleDenominator; } 

Use NullPointerException quando necessário

Se possível, execute suas próprias verificações de nulos; lançando um IllegalArgumentException ou NullPointerException com informações detalhadas sobre o que deu errado.

 public double toScale( Integer scaleDenominator ){ if( scaleDenominator == null ){ throw new NullPointerException( "scaleDenominator must be provided"); } if( scaleDenominator > 0 ){ throw new IllegalArgumentException( "scaleDenominator must be greater than 0"); } return 1 / (double) scaleDenominator; } 

Você não está otimizando um biiiiiiiiiiiiiiit prematuramente !?

Eu usaria apenas o primeiro. É claro e conciso.

Eu raramente trabalho com Java, mas eu suponho que há uma maneira de fazer Assert operar apenas em compilações de debugging, então isso seria um não-não.

O terceiro me dá arrepios, e acho que eu iria recorrer imediatamente à violência se vi em código. Não está claro o que está fazendo.

Você não deveria estar jogando NullPointerException. Se você quiser um NullPointerException, apenas não marque o valor e ele será lançado automaticamente quando o parâmetro for nulo e você tentar cancelar a referência.

Confira as classs validate e StringUtils do apache commons lang.
Validate.notNull(variable) irá lançar um IllegalArgumentException se “variável” for nulo.
Validate.notEmpty(variable) lançará uma IllegalArgumentException se “variável” estiver vazia (comprimento nulo ou zero “.
Talvez até melhor:
String trimmedValue = StringUtils.trimToEmpty(variable) garante que “trimmedValue” nunca é nulo. Se “variável” for nulo, “trimmedValue” será a string vazia (“”).

Você pode usar a class de utilitário de objects.

 public void method1(String arg) { Objects.requireNonNull(arg); } 

consulte http://docs.oracle.com/javase/7/docs/api/java/util/Objects.html#requireNonNull%28T%29

O primeiro método é a minha preferência, porque transmite as maiores intenções. Muitas vezes há atalhos que podem ser tomados na programação, mas minha opinião é que o código mais curto nem sempre é um código melhor.

Na minha opinião, existem três questões com o terceiro método:

  1. A intenção não é clara para o leitor casual.
  2. Mesmo que você tenha informações de números de linha, os números de linha mudam. Em um sistema de produção real, saber que houve um problema no SomeClass na linha 100 não fornece todas as informações de que você precisa. Você também precisa saber a revisão do arquivo em questão e ser capaz de chegar a essa revisão. Tudo sumdo, um monte de problemas para o que parece ser muito pouco benefício.
  3. Não está claro por que você acha que a chamada para arg.getClass pode ser otimizada. É um método nativo. A menos que o HotSpot seja codificado para ter um conhecimento específico do método para essa eventualidade, provavelmente ele deixará a chamada em paz, já que não pode saber sobre possíveis efeitos colaterais do código C que é chamado.

Minha preferência é usar o número 1 sempre que achar que há necessidade de uma verificação nula. Ter o nome da variável na mensagem de erro é ótimo para descobrir rapidamente o que exatamente deu errado.

PS Não acho que otimizar o número de tokens no arquivo de origem seja um critério muito útil.

x == null é super rápido, e pode ser um par de clocks de CPU (incluindo a predição de ramificação que terá sucesso). AssertNotNull será inlined, então não há diferença.

x.getClass () não deve ser mais rápido que x == null, mesmo que use trap. (razão: o x estará em algum registrador e checando um registrador versus um valor imediato é rápido, o ramo também será predito apropriadamente)

Resumindo: a menos que você faça algo realmente estranho, ele será otimizado pela JVM.

A primeira opção é a mais fácil e também a mais clara.

Não é comum em Java, mas em C e C ++ onde o operador = pode ser incluído em uma expressão na instrução if e, portanto, levar a erros, geralmente é recomendado trocar de lugar entre a variável e a constante da seguinte forma:

 if (NULL == variable) { ... } 

ao invés de:

 if (variable == NULL) { ... } 

evitando erros do tipo:

 if (variable = NULL) { // Assignment! ... } 

Se você fizer a alteração, o compilador encontrará esse tipo de erro para você.

Eu usaria o mecanismo interno de declaração de Java.

 assert arg != null; 

A vantagem disso em todos os outros methods é que ele pode ser desligado.

Eu prefiro o método 4, 5 ou 6, com o # 4 sendo aplicado a methods públicos da API e 5/6 para methods internos, embora o # 6 seja aplicado com mais freqüência a methods públicos.

 /** * Method 4. * @param arg A String that should have some method called upon it. Will be ignored if * null, empty or whitespace only. */ public void method4(String arg) { // commons stringutils if (StringUtils.isNotBlank(arg) { arg.trim(); } } /** * Method 5. * @param arg A String that should have some method called upon it. Shouldn't be null. */ public void method5(String arg) { // Let NPE sort 'em out. arg.trim(); } /** * Method 6. * @param arg A String that should have some method called upon it. Shouldn't be null. */ public void method5(String arg) { // use asserts, expect asserts to be enabled during dev-time, so that developers // that refuse to read the documentations get slapped on the wrist for still passing // null. Assert is a no-op if the -ae param is not passed to the jvm, so 0 overhead. assert arg != null : "Arg cannot be null"; // insert insult here. arg.trim(); } 

A melhor solução para manipular nulos é não usar valores nulos. Envolva methods de terceiros ou de biblioteca que possam retornar valores nulos com proteções nulas, substituindo o valor por algo que faça sentido (como uma string vazia), mas não faça nada quando usado. Lance NPE’s se um nulo realmente não deve ser passado, especialmente em methods setter onde o object passado não é chamado imediatamente.

Não há votos para este, mas eu uso uma ligeira variação de # 2, como

 erStr += nullCheck (varName, String errMsg); // returns formatted error message 

Fundamentação da petição: (1) Eu posso fazer um loop sobre vários argumentos, (2) O método nullCheck está escondido em uma superclass e (3) no final do loop,

 if (erStr.length() > 0) // Send out complete error message to client else // do stuff with variables 

No método da superclass, seu # 3 parece legal, mas eu não lançaria uma exceção (o que é o ponto, alguém tem que lidar com isso, e como um contêiner de servlet, o tomcat irá ignorá-lo, então pode ser assim ( )) Atenciosamente, – MS

Primeiro método. Eu nunca faria o segundo ou o terceiro método, a menos que eles sejam implementados de forma eficiente pela JVM subjacente. Caso contrário, esses dois são apenas excelentes exemplos de otimização prematura (com o terceiro tendo uma possível penalidade de desempenho – você não quer estar lidando e acessando meta-dados de class em pontos de access gerais).

O problema com os NPEs é que eles são coisas que cruzam muitos aspectos da programação (e meus aspectos, quero dizer algo mais profundo e profundo que o AOP). É um problema de design de linguagem (não dizer que a linguagem é ruim, mas que é uma falha fundamental … de qualquer linguagem que permita pointers nulos ou referências).

Como tal, é melhor simplesmente lidar com isso explicitamente como no primeiro método. Todos os outros methods são (com falha) tentativas de simplificar um modelo de operações, uma complexidade inevitável que existe no modelo de programação subjacente.

É uma bala que não podemos evitar morder. Lidar com isso explicitamente como é – no caso geral que é – o menos doloroso no caminho.

Embora eu concorde com o consenso geral de preferir evitar o hack getClass (), vale a pena notar que, a partir do OpenJDK versão 1.8.0_121, o javac usará o hack getClass () para inserir verificações nulas antes de criar expressões lambda. Por exemplo, considere:

 public class NullCheck { public static void main(String[] args) { Object o = null; Runnable r = o::hashCode; } } 

Depois de compilar isto com o javac, você pode usar o javap para ver o bytecode rodando o javap -c NullCheck . A saída é (em parte):

 Compiled from "NullCheck.java" public class NullCheck { public NullCheck(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return public static void main(java.lang.String[]); Code: 0: aconst_null 1: astore_1 2: aload_1 3: dup 4: invokevirtual #2 // Method java/lang/Object.getClass:()Ljava/lang/Class; 7: pop 8: invokedynamic #3, 0 // InvokeDynamic #0:run:(Ljava/lang/Object;)Ljava/lang/Runnable; 13: astore_2 14: return } 

O conjunto de instruções em “linhas” 3, 4 e 7 basicamente invoca o.getClass () e descarta o resultado. Se você executar o NullCheck, receberá um NullPointerException da linha 4.

Se isso é algo que o pessoal do Java concluiu foi uma otimização necessária, ou é apenas um hack barato, eu não sei. No entanto, com base no comentário de John Rose em https://bugs.openjdk.java.net/browse/JDK-8042127?focusedCommentId=13612451&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-13612451 , Eu suspeito que, de fato, pode ser o caso de que o hack getClass (), que produz uma verificação nula implícita, possa ter um desempenho um pouco melhor que sua contraparte explícita. Dito isso, eu evitaria usá-lo, a menos que um benchmarking cuidadoso mostrasse que ele fazia qualquer diferença significativa.

(Curiosamente, o Eclipse Compiler para Java (ECJ) não inclui essa verificação nula e a execução do NullCheck, conforme compilado pelo ECJ, não lançará um NPE.)

Eu acredito que o quarto e o padrão mais útil é não fazer nada. Seu código lançará NullPointerException ou outra exceção um par de linhas depois (se null for um valor ilegal) e funcionará bem se null for OK neste contexto.

Acredito que você deve executar a verificação nula somente se tiver algo a ver com isso. A verificação para lançar exceção é irrelevante na maioria dos casos. Só não esqueça de mencionar no javadoc se o parâmetro pode ser nulo.