Por que alguém deve usar Objects.requireNonNull ()?


214

Observei que muitos métodos Java 8 no Oracle JDK usam Objects.requireNonNull(), que lançam internamente NullPointerExceptionse o objeto fornecido (argumento) for null.

public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}

Mas NullPointerExceptionserá lançado de qualquer maneira se um nullobjeto for desreferenciado. Então, por que alguém deveria fazer esse teste nulo extra e jogar NullPointerException?

Uma resposta óbvia (ou benefício) é que torna o código mais legível e eu concordo. Estou interessado em conhecer outras razões para usar Objects.requireNonNull()no início do método.




1
Essa abordagem da verificação de argumentos permite trapacear quando você está escrevendo testes de unidade. O Spring também possui utilitários como esse (consulte docs.spring.io/spring/docs/current/javadoc-api/org/… ). Se você possui um "se" e deseja obter uma alta cobertura de teste de unidade, deve cobrir os dois ramos: quando a condição for atendida e quando não for atendida. Se você usar Objects.requireNonNull, seu código não possui ramificação, portanto, uma única passagem em um teste de unidade terá 100% de cobertura :-)
xorcus

Respostas:


214

Porque você pode tornar as coisas explícitas ao fazer isso. Gostar:

public class Foo {
  private final Bar bar;

  public Foo(Bar bar) {
    Objects.requireNonNull(bar, "bar must not be null");
    this.bar = bar;
  }

Ou mais curto:

  this.bar = Objects.requireNonNull(bar, "bar must not be null");

Agora você sabe :

  • quando um objeto Foo foi criado com sucesso usandonew()
  • em seguida, a sua barra de campo é garantida ser não-nulo.

Compare isso com: você cria um objeto Foo hoje e amanhã invoca um método que usa esse campo e lança. Provavelmente, você não saberá amanhã por que essa referência foi nula ontem quando foi passada para o construtor!

Em outras palavras: usando explicitamente esse método para verificar as referências recebidas, você pode controlar o momento em que a exceção será lançada. E na maioria das vezes, você deseja falhar o mais rápido possível !

As principais vantagens são:

  • como dito, comportamento controlado
  • depuração mais fácil - porque você vomita no contexto da criação do objeto. Em um momento em que você tem uma certa chance de que seus logs / rastreamentos lhe digam o que deu errado!
  • e, como mostrado acima: o verdadeiro poder dessa idéia se desdobra em conjunto com os campos finais . Porque agora qualquer outro código da sua classe pode assumir com segurança que barnão é nulo - e, portanto, você não precisa de nenhuma if (bar == null)verificação em outros lugares!

23
Você pode tornar seu código mais compacto escrevendo #this.bar = Objects.requireNonNull(bar, "bar must not be null");
Kirill Rakhman

3
@KirillRakhman Ensinando algo do GhostCat que eu não conhecia -> ganhar um ingresso na loteria do GhostCat.
GhostCat

1
Eu acho que o comentário # 1 aqui é o benefício real de Objects.requireNonNull(bar, "bar must not be null");. Obrigado por este.
Kawu

1
Qual foi o primeiro e por que as pessoas da "língua" devem seguir os autores de alguma biblioteca ?! Por que a goiaba não segue o comportamento do Java lang ?!
GhostCat 24/02/19

1
O this.bar = Objects.requireNonNull(bar, "bar must not be null");é ok em construtores, mas potencialmente perigosa em outros métodos, se duas ou mais variáveis são definidas no mesmo método. Exemplo: talkwards.com/2018/11/03/…
Erk

100

Fail-fast

O código deve falhar o mais rápido possível. Ele não deve executar metade do trabalho e, em seguida, desreferenciar o nulo e travar, deixando apenas metade do trabalho concluído, fazendo com que o sistema esteja em um estado inválido.

Isso geralmente é chamado de "falha antecipada" ou "falha rápida" .


40

Mas NullPointerException será lançada de qualquer maneira se um objeto nulo for desreferenciado. Então, por que alguém deveria fazer essa verificação nula extra e lançar NullPointerException?

Isso significa que você detecta o problema imediatamente e de forma confiável .

Considerar:

  • A referência não pode ser usada até mais tarde no método, depois que seu código já tiver realizado alguns efeitos colaterais
  • A referência não pode ser desreferenciada neste método
    • Pode ser passado para um código completamente diferente (ou seja, causa e erro estão distantes no espaço do código)
    • Pode ser usado muito mais tarde (ou seja, causa e erro estão distantes no tempo)
  • Pode ser usado em algum lugar em que uma referência nula é válida, mas tem um efeito não intencional

O .NET melhora isso ao separar NullReferenceException("você desferiu um valor nulo") de ArgumentNullException("você não deveria ter passado nulo como argumento - e foi para esse parâmetro). Gostaria que o Java fizesse a mesma coisa, mas mesmo com apenas a NullPointerException, ainda é muito mais fácil corrigir o código se o erro for gerado no ponto mais inicial em que ele pode ser detectado.


13

Usar requireNonNull()como primeiras instruções em um método permite identificar agora / rapidamente a causa da exceção.
O rastreamento de pilha indica claramente que a exceção foi lançada assim que a entrada do método porque o chamador não respeitou os requisitos / contrato. Passar um nullobjeto para outro método pode, de fato, provocar uma exceção por vez, mas a causa do problema pode ser mais complicada de entender, pois a exceção será lançada em uma invocação específica no nullobjeto que pode ser muito mais longe.


Aqui está um exemplo concreto e real que mostra por que temos que favorecer a falha rápida em geral e, mais particularmente, usando Object.requireNonNull()ou qualquer maneira de executar uma verificação sem nulo nos parâmetros projetados para não ser null.

Suponha que uma Dictionaryclasse que compõe a LookupServicee a Listde Stringpalavras representativas contidas em. Esses campos foram projetados para não serem nulle um deles é passado no Dictionary construtor.

Agora suponha uma implementação "ruim" de Dictionarysem nullverificação na entrada do método (aqui é o construtor):

public class Dictionary {

    private final List<String> words;
    private final LookupService lookupService;

    public Dictionary(List<String> words) {
        this.words = this.words;
        this.lookupService = new LookupService(words);
    }

    public boolean isFirstElement(String userData) {
        return lookupService.isFirstElement(userData);
    }        
}


public class LookupService {

    List<String> words;

    public LookupService(List<String> words) {
        this.words = words;
    }

    public boolean isFirstElement(String userData) {
        return words.get(0).contains(userData);
    }
}

Agora, vamos chamar o Dictionaryconstrutor com uma nullreferência para o wordsparâmetro:

Dictionary dictionary = new Dictionary(null); 

// exception thrown lately : only in the next statement
boolean isFirstElement = dictionary.isFirstElement("anyThing");

A JVM lança o NPE nesta declaração:

return words.get(0).contains(userData); 
Exceção no encadeamento "main" java.lang.NullPointerException
    em LookupService.isFirstElement (LookupService.java:5)
    em Dictionary.isFirstElement (Dictionary.java:15)
    em Dictionary.main (Dictionary.java:22)

A exceção é acionada na LookupServiceclasse enquanto a origem dela é bem anterior (o Dictionaryconstrutor). Isso torna a análise geral do problema muito menos óbvia.
É words null? É words.get(0) null? Ambos ? Por que um, o outro ou talvez os dois são null? É um erro de codificação no Dictionary(construtor? Método invocado?)? É um erro de codificação LookupService? (construtor? método invocado?)?
Finalmente, teremos que inspecionar mais código para encontrar a origem do erro e, em uma classe mais complexa, talvez até use um depurador para entender mais facilmente o que aconteceu.
Mas por que uma coisa simples (falta de verificação nula) se torna um problema complexo?
Porque permitimos que o bug / falta inicial fosse identificável em um vazamento de componente específico nos componentes inferiores.
Imagine que LookupServicenão era um serviço local, mas um serviço remoto ou uma biblioteca de terceiros com poucas informações de depuração ou imagine que você não tinha 2 camadas, mas 4 ou 5 camadas de invocações de objetos antes de nullserem detectadas? O problema seria ainda mais complexo de analisar.

Portanto, a maneira de favorecer é:

public Dictionary(List<String> words) {
    this.words = Objects.requireNonNull(words);
    this.lookupService = new LookupService(words);
}

Dessa forma, sem dor de cabeça: recebemos a exceção lançada assim que isso é recebido:

// exception thrown early : in the constructor 
Dictionary dictionary = new Dictionary(null);

// we never arrive here
boolean isFirstElement = dictionary.isFirstElement("anyThing");
Exceção no encadeamento "main" java.lang.NullPointerException
    em java.util.Objects.requireNonNull (Objects.java:203)
    em com.Dictionary. (Dictionary.java:15)
    em com.Dictionary.main (Dictionary.java:24)

Observe que aqui ilustrei o problema com um construtor, mas uma chamada de método pode ter a mesma restrição de verificação não nula.


Uau! Ótima explicação.
Jonathas Nascimento

2

Como uma observação lateral, isso falhou rapidamente antes Object#requireNotNullfoi implementado um pouco diferente do java-9 dentro de algumas das próprias classes jre. Suponha o caso:

 Consumer<String> consumer = System.out::println;

No java-8 isso compila como (apenas as partes relevantes)

getstatic Field java/lang/System.out
invokevirtual java/lang/Object.getClass

Basicamente, uma operação como: yourReference.getClass- que falharia se sua referência for null.

As coisas mudaram no jdk-9, onde o mesmo código é compilado como

getstatic Field java/lang/System.out
invokestatic java/util/Objects.requireNonNull

Ou basicamente Objects.requireNotNull (yourReference)


1

O uso básico está verificando e jogando NullPointerExceptionimediatamente.

Uma alternativa melhor (atalho) para atender ao mesmo requisito é a anotação @NonNull do lombok.


1
A anotação não substitui o método Objetos, eles trabalham juntos. Você não pode dizer @ NonNull x = couldBeNull, você diria @ NonNull x = Objects.requireNonNull (couldBeNull, "inconcebible!");
Bill K

@BillK desculpe, eu não entendo u
Supun Wijerathne

Eu estava apenas dizendo que a anotação Nonnull funciona com requireNonNull, não é uma alternativa, mas eles funcionam muito bem juntos.
Bill K

implementar a natureza rápida, é uma alternativa, certo? Sim, eu concordo que não é uma alternativa para a tarefa.
Supun Wijerathne 29/08/19

Acho que chamaria requireNonNull () de "conversão" de @ Nullable para @ NonNull. Se você não usar as anotações, o método não será realmente muito interessante (já que tudo o que faz é lançar um NPE exatamente como o código que ele protegeria) - embora ele mostre claramente sua intenção.
Bill K

1

A exceção de ponteiro nulo é lançada quando você acessa um membro de um objeto que está nullem um momento posterior. Objects.requireNonNull()verifica imediatamente o valor e lança a exceção instantaneamente sem avançar.


0

Eu acho que deve ser usado em construtores de cópia e em alguns outros casos como DI, cujo parâmetro de entrada é um objeto, você deve verificar se o parâmetro é nulo. Em tais circunstâncias, você pode usar esse método estático convenientemente.


0

No contexto de extensões de compilador que implementam a verificação de Nullability (por exemplo: uber / NullAway ), Objects.requireNonNulldeve ser usado com moderação em ocasiões em que você tem um campo anulável que você sabe que não é nulo em um determinado ponto do seu código.

Dessa maneira, existem dois usos principais:

  • Validação

    • já coberto por outras respostas aqui
    • verificação de tempo de execução com sobrecarga e potencial NPE
  • Marcação de anulabilidade (mudando de @Nullable para @Nonnull)

    • uso mínimo da verificação de tempo de execução em favor da verificação em tempo de compilação
    • só funciona quando as anotações estão corretas (aplicadas pelo compilador)

Exemplo de uso da marcação Anulabilidade:

@Nullable
Foo getFoo(boolean getNull) { return getNull ? null : new Foo(); }

// Changes contract from Nullable to Nonnull without compiler error
@Nonnull Foo myFoo = Objects.requireNonNull(getFoo(false));

0

Além de todas as respostas corretas:

Nós o usamos em fluxos reativos. Geralmente o resultadoNullpointerException s são agrupados em outras exceções, dependendo da ocorrência no fluxo. Portanto, mais tarde podemos decidir facilmente como lidar com o erro.

Apenas um exemplo: imagine que você tem

<T> T parseAndValidate(String payload) throws ParsingException { ... };
<T> T save(T t) throws DBAccessException { ... };

onde parseAndValidateenvolve o NullPointerExceptionde requireNonNullem um ParsingException.

Agora você pode decidir, por exemplo, quando fazer uma nova tentativa ou não:

...
.map(this::parseAndValidate)
.map(this::save)
.retry(Retry.<T>allBut(ParsingException.class))

Sem a verificação, a NullPointerException ocorrerá no diretório save método, o que resultará em inúmeras tentativas. Mesmo vale a pena, imagine uma assinatura longa vida, adicionando

.onErrorContinue(
    throwable -> throwable.getClass().equals(ParsingException.class),
    parsingExceptionConsumer()
)

Agora o RetryExhaustExceptionmatará sua assinatura.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.