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 null
objeto 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 null
objeto 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 Dictionary
classe que compõe a LookupService
e a List
de String
palavras representativas contidas em. Esses campos foram projetados para não serem null
e um deles é passado no Dictionary
construtor.
Agora suponha uma implementação "ruim" de Dictionary
sem null
verificaçã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 Dictionary
construtor com uma null
referência para o words
parâ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 LookupService
classe enquanto a origem dela é bem anterior (o Dictionary
construtor). 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 LookupService
nã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 null
serem 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.