J'ai noté que de nombreuses méthodes Java 8 dans Oracle JDK utilisent Objects.requireNonNull()
, qui lève en interne NullPointerException
si l'objet (l'argument) donné est null
.
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
Mais NullPointerException
sera quand même levé si un objet null
est déréférencé. Alors, pourquoi devrait-on faire cette vérification nulle supplémentaire et lancer NullPointerException
?
Une réponse évidente (ou un avantage) est que cela rend le code plus lisible et je suis d’accord. Je souhaite connaître d'autres raisons d'utiliser Objects.requireNonNull()
au début de la méthode.
Parce que vous pouvez faire des choses explicite en le faisant. Comme:
public class Foo {
private final Bar bar;
public Foo(Bar bar) {
Objects.requireNonNull(bar, "bar must not be null");
this.bar = bar;
}
Ou plus court:
this.bar = Objects.requireNonNull(bar, "bar must not be null");
Maintenant vous savez:
new()
Comparez cela à: vous créez un objet Foo aujourd'hui, et demain vous appelez une méthode qui utilise ce champ et le jette. Très probablement, vous ne saurez pas demain pourquoi cette référence était nulle hier lorsqu'elle a été transmise au constructeur!
En d'autres termes: en utilisant explicitement cette méthode pour vérifier les références entrantes , vous pouvez contrôler l'heure à laquelle l'exception sera jeté. Et la plupart du temps, vous voulez échouer aussi vite que possible !
Les principaux avantages sont:
bar
n'est pas nul - et donc vous n'avez pas besoin de if (bar == null)
vérifie ailleurs!Le code devrait planter dès que possible. Il ne doit pas effectuer la moitié du travail, puis déréférencer la valeur null et planter, laissant ensuite la moitié du travail effectué, ce qui a pour effet de rendre le système invalide.
Ceci est communément appelé "échec précoce" ou "échec rapide" .
Mais NullPointerException sera quand même levé si un objet null est déréférencé. Alors, pourquoi devrait-on faire cette vérification null supplémentaire et lancer NullPointerException?
Cela signifie que vous détectez le problème immédiatement et de manière fiable.
Considérer:
.NET améliore cela en séparant NullReferenceException
("vous avez déréférencé une valeur null") de ArgumentNullException
("vous n'auriez pas dû passer null comme argument - et c'était pour this paramètre). Je souhaite que Java fasse la même chose, mais même avec juste un NullPointerException
, il est toujours beaucoup plus facile à corriger le code si l'erreur est renvoyée au plus tôt au moment où il peut être détecté.
Utiliser requireNonNull()
comme premières instructions d'une méthode permet d'identifier maintenant/rapidement la cause de l'exception.
Le stacktrace indique clairement que l'exception a été levée dès l'entrée de la méthode car l'appelant n'a pas respecté les exigences/le contrat. Passage d'un objet null
à une autre méthode peut provoquer une exception à la fois mais la cause du problème peut être plus compliquée à comprendre car l'exception sera renvoyée dans une invocation spécifique sur l'objet null
qui peut être beaucoup plus loin.
Voici un exemple concret et réel qui montre pourquoi nous devons privilégier les échecs rapides en général et plus particulièrement en utilisant Object.requireNonNull()
ou tout autre moyen d'effectuer un contrôle nul sur des paramètres conçus pour ne pas être null
.
Supposons une classe Dictionary
qui compose une LookupService
et une List
de String
mots représentant des mots contenus dans. Ces champs sont conçus pour être non null
et l'un de ceux-ci est passé dans le constructeur Dictionary
.
Supposons maintenant une "mauvaise" implémentation de Dictionary
sans null
cocher dans l'entrée de la méthode (ici, le constructeur):
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);
}
}
Appelons maintenant le constructeur Dictionary
avec une référence null
pour le paramètre words
:
Dictionary dictionary = new Dictionary(null);
// exception thrown lately : only in the next statement
boolean isFirstElement = dictionary.isFirstElement("anyThing");
La JVM envoie le NPE à cette déclaration:
return words.get(0).contains(userData);
Exception dans le fil "principal" Java.lang.NullPointerException Sur LookupService.isFirstElement (LookupService.Java:5) Sur Dictionary.isFirstElement (Dictionary.Java:15) at Dictionary.main (Dictionary.Java:22)
L'exception est déclenchée dans la classe LookupService
alors que son origine est bien antérieure (le constructeur Dictionary
.). Cela rend l’analyse globale des problèmes beaucoup moins évidente.
Est-ce que words
null
? Est-ce que words.get(0) null
? Tous les deux ? Pourquoi l'un, l'autre ou peut-être les deux sont null
? Est-ce une erreur de codage dans Dictionary
(constructeur? Méthode invoquée?)? Est-ce une erreur de codage dans LookupService
? (constructeur? méthode invoquée?)
Enfin, nous devrons inspecter plus de code pour rechercher l’erreur Origin et dans une classe plus complexe, utiliser même un débogueur pour comprendre plus facilement ce qui s’est passé.
Mais pourquoi une chose simple (l’absence de contrôle nul) devient-elle complexe?
Parce que nous avons laissé le bogue/manque initial identifiable sur une fuite de composant spécifique sur les composants inférieurs.
Imaginons que LookupService
n’était pas un service local, mais un service distant ou une bibliothèque tierce contenant peu d’informations de débogage ou imaginiez que vous n’aviez pas 2 couches mais 4 ou 5 couches d’appels d’objets auparavant. le null
être détecté? Le problème serait encore plus complexe à analyser.
Donc, la façon de favoriser est:
public Dictionary(List<String> words) {
this.words = Objects.requireNonNull(words);
this.lookupService = new LookupService(words);
}
De cette façon, pas de mal de tête: nous obtenons l'exception dès qu'elle est reçue:
// exception thrown early : in the constructor
Dictionary dictionary = new Dictionary(null);
// we never arrive here
boolean isFirstElement = dictionary.isFirstElement("anyThing");
Exception dans le fil "principal" Java.lang.NullPointerException Sur Java.util.Objects.requireNonNull (Objects.Java:203) Sur com.Dictionary. (Dictionary.Java : 15) Sur com.Dictionary.main (Dictionary.Java:24)
Notez qu'ici j'ai illustré le problème avec un constructeur, mais un appel de méthode pourrait avoir la même contrainte de vérification non nulle.
En passant, cette défaillance rapide avant que Object#requireNotNull
soit implémentée de manière légèrement différente avant Java-9 dans certaines des classes jre elles-mêmes. Supposons le cas:
Consumer<String> consumer = System.out::println;
En Java-8, cela compile en tant que (seulement les parties pertinentes)
getstatic Field Java/lang/System.out
invokevirtual Java/lang/Object.getClass
Fondamentalement, une opération telle que: yourReference.getClass
- qui échouerait si votre référence était null
.
Les choses ont changé dans jdk-9 où le même code est compilé que
getstatic Field Java/lang/System.out
invokestatic Java/util/Objects.requireNonNull
Ou fondamentalement Objects.requireNotNull (yourReference)
L'utilisation de base consiste à vérifier et à lancer NullPointerException
immédiatement.
Une meilleure alternative (raccourci) pour répondre à la même exigence est @ NonNull annotation par lombok.