web-dev-qa-db-fra.com

Pourquoi utiliser Objects.requireNonNull ()?

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.

132
user4686046

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:

  • quand un objet Foo a été créé avec succès avec new()
  • puis sa barre est garanti être non nul.

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:

  • comme dit, contrôlé comportement
  • un débogage plus facile - parce que vous vomissez dans le contexte de la création d'objet. À un moment où vous avez une certaine chance que vos traces/journaux vous disent ce qui ne va pas!
  • et comme indiqué ci-dessus: le véritable pouvoir de cette idée se déploie conjointement avec les champs finaux . Parce que maintenant , tout autre code de votre classe peut sans risque supposer que bar n'est pas nul - et donc vous n'avez pas besoin de if (bar == null) vérifie ailleurs!
139
GhostCat

Fail-fast

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" .

70
luk2302

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:

  • La référence ne peut être utilisée que plus tard dans la méthode, après que votre code a déjà effectué des effets secondaires.
  • La référence peut ne pas être déréférencée dans cette méthode
    • Il pourrait être transmis à un code complètement différent (c'est-à-dire que la cause et l'erreur sont éloignées l'une de l'autre dans l'espace de code)
    • Il pourrait être utilisé beaucoup plus tard (c'est-à-dire que la cause et l'erreur sont très éloignées l'une de l'autre)
  • Il peut être utilisé quelque part qu'une référence null est valide, mais a un effet inattendu

.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é.

30
Jon Skeet

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 wordsnull? 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.

6
davidxxx

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)

1
Eugene

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.

0
Supun Wijerathne