403 votes

Pourquoi utiliser Objects.requireNonNull() ?

J'ai remarqué que de nombreuses méthodes Java 8 dans le JDK d'Oracle utilisent Objects.requireNonNull() qui lance en interne NullPointerException si l'objet donné (argument) est null .

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

Mais NullPointerException sera lancé de toute façon si un null est déréférencé. Alors, pourquoi devrait-on faire cette vérification supplémentaire de la nullité et jeter NullPointerException ?

Une réponse évidente (ou un avantage) est que cela rend le code plus lisible et je suis d'accord. Je suis curieux de connaître les autres raisons qui justifient l'utilisation de Objects.requireNonNull() au début de la méthode.

40 votes

2 votes

Cette approche de la vérification des arguments vous permet de tricher lorsque vous écrivez des tests unitaires. Spring dispose également d'utilitaires de ce type (voir docs.spring.io/spring/docs/current/javadoc-api/org/ ). Si vous avez un "if" et que vous voulez avoir une couverture de test unitaire élevée, vous devez couvrir les deux branches : lorsque la condition est remplie, et lorsque la condition n'est pas remplie. Si vous utilisez Objects.requireNonNull votre code n'a pas de ramification, donc un seul passage d'un test unitaire vous permettra d'obtenir une couverture de 100% :-)

373voto

GhostCat Points 83269

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 savoir :

  • quand un objet Foo a été créé avec succès en utilisant new()
  • puis son bar est garantie ne soit pas nulle.

Comparez cela à : vous créez un objet Foo aujourd'hui, et demain vous invoquez une méthode qui utilise ce champ et lance. Il est fort probable que vous ne sachiez pas demain pourquoi cette référence était nulle hier lorsqu'il a été transmis au constructeur !

En d'autres termes : en utilisant explicitement cette méthode pour vérifier entrant les références que vous pouvez contrôle le moment où l'exception sera levée. Et la plupart du temps, vous voulez échouer le plus rapidement possible !

Les principaux avantages sont les suivants :

  • a dit, contrôlé comportement
  • un débogage plus facile - parce que vous lancez dans le contexte de la création de l'objet. A un moment où vous avez une certaine chance que vos logs/traces vous indiquent ce qui n'a pas fonctionné !
  • et comme indiqué ci-dessus : la véritable puissance de cette idée se déploie en conjonction avec final champs. Parce que maintenant tout autre code dans votre classe peut supposer en toute sécurité que bar n'est pas null - et donc vous n'avez pas besoin de if (bar == null) des contrôles dans d'autres lieux !

48 votes

Vous pouvez rendre votre code plus compact en écrivant this.bar = Objects.requireNonNull(bar, "bar must not be null");

5 votes

@KirillRakhman Apprendre à GhostCat quelque chose de cool que je ne connaissais pas --> gagner un ticket à la loterie d'upvote de GhostCat.

2 votes

Je pense que le commentaire n° 1 ici est l'avantage réel de la Objects.requireNonNull(bar, "bar must not be null"); . Merci pour celui-ci.

157voto

luk2302 Points 31354

Fail-fast

Le code doit s'effondrer le plus rapidement possible. Il doit no faire la moitié du travail, déréférencer le null et seulement ensuite se planter, en laissant la moitié du travail effectué, ce qui fait que le système est dans un état invalide.

C'est ce que l'on appelle communément "l'échec précoce" ou "l'échec rapide".

0 votes

Je crois que c'est la réponse précise à la question.

50voto

Jon Skeet Points 692016

Mais l'exception NullPointerException sera de toute façon levée si un objet nul est déréférencé. Alors, pourquoi devrait-on faire cette vérification supplémentaire de l'existence d'un objet nul et lancer une exception NullPointerException ?

Cela signifie que vous détectez le problème immédiatement y de manière fiable .

Envisager :

  • La référence peut n'être utilisée que plus tard dans la méthode, après que votre code a déjà produit des effets secondaires.
  • La référence ne peut pas du tout ê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 très éloignées dans l'espace de code).
    • Elle pourrait être utilisée beaucoup plus tard (c'est-à-dire que la cause et l'erreur sont très éloignées dans le temps).
  • Il peut être utilisé à un endroit où une référence nulle est valable, mais a un effet non désiré

.NET améliore cette situation en séparant NullReferenceException ("vous avez déréférencé une valeur nulle") de ArgumentNullException ("vous n'auriez pas dû passer null comme argument - et c'était pour este ). J'aimerais que Java fasse la même chose, mais même avec un simple NullPointerException , c'est encore beaucoup Il est plus facile de corriger le code si l'erreur est déclenchée au premier moment où elle peut être détectée.

26voto

davidh Points 107

L'utilisation requireNonNull() en tant que premières déclarations d'une méthode permettent d'identifier immédiatement/rapidement la cause de l'exception.
Le suivi de la pile indique clairement que l'exception a été levée dès l'entrée de la méthode parce que l'appelant n'a pas respecté les exigences/le contrat. Transmission d'un null à une autre méthode peut provoque en effet une exception à un moment donné, mais la cause du problème peut être plus compliquée à comprendre, car l'exception sera levée lors d'une invocation spécifique de la fonction null qui peut être beaucoup plus éloigné.


Voici un exemple concret et réel qui montre pourquoi il faut privilégier l'échec rapide en général et plus particulièrement l'utilisation d'un système de gestion des risques. Object.requireNonNull() ou tout autre moyen d'effectuer une vérification de l'absence de nullité sur des paramètres conçus pour ne pas l'être null .

Supposons qu'un Dictionary qui compose un LookupService et un List de String représentant les mots contenus dans. Ces champs sont conçus pour ne pas être null et l'un d'entre eux est transmis dans le Dictionary constructeur.

Supposons maintenant qu'une "mauvaise" implémentation de Dictionary sans null vérifier dans l'entrée de la méthode (ici c'est 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);
    }
}

Invoquons maintenant la fonction Dictionary avec un constructeur null référence pour le words paramètre :

Dictionary dictionary = new Dictionary(null); 

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

La JVM lance le NPE à cette déclaration :

return words.get(0).contains(userData); 

Exception in thread "main" java.lang.NullPointerException
    at LookupService.isFirstElement(LookupService.java:5)
    at Dictionary.isFirstElement(Dictionary.java:15)
    at Dictionary.main(Dictionary.java:22)

L'exception est déclenchée dans le LookupService alors que l'origine de celle-ci est bien antérieure (la classe Dictionary ). Cela rend l'analyse globale du problème beaucoup moins évidente.
Est words null ? Est-ce que words.get(0) null ? Les deux ? Pourquoi l'un, l'autre ou peut-être les deux ? null ? S'agit-il d'une erreur de codage dans Dictionary (constructeur ? méthode invoquée ?) ? S'agit-il d'une erreur de codage dans LookupService ? (constructeur ? méthode invoquée ?) ?
Enfin, nous devrons inspecter plus de code pour trouver l'origine de l'erreur et, dans une classe plus complexe, peut-être même utiliser un débogueur pour comprendre plus facilement ce qui s'est passé.
Mais pourquoi une chose simple (l'absence de vérification de la nullité) devient-elle un problème complexe ?
Parce que nous avons laissé le bogue initial/le manque identifiable sur un composant spécifique fuir sur les composants inférieurs.
Imaginez que LookupService n'était pas un service local mais un service distant ou une bibliothèque tierce avec peu d'informations de débogage ou imaginez que vous n'ayez pas 2 couches mais 4 ou 5 couches d'invocations d'objets avant que la fonction null peuvent-ils être détectés ? Le problème serait encore plus complexe à analyser.

Le moyen de favoriser est donc :

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

De cette manière, pas de prise de tête : nous obtenons l'exception levée 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 in thread "main" java.lang.NullPointerException
    at java.util.Objects.requireNonNull(Objects.java:203)
    at com.Dictionary.(Dictionary.java:15)
    at com.Dictionary.main(Dictionary.java:24)

Notez que j'ai illustré le problème avec un constructeur, mais qu'une invocation de méthode pourrait avoir la même contrainte de vérification non nulle.

2 votes

Ouah ! Excellente explication.

2 votes

Je suis d'accord avec @JonathasNascimento, très bonne explication et utilisation d'exemples.

2voto

Eugene Points 6271

Par ailleurs, cette procédure d'urgence a été mise en place avant Object#requireNotNull a été mis en œuvre de manière légèrement différente avant Java-9 dans certaines des classes JRE elles-mêmes. Supposons que le cas :

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

En Java-8, cela se compile comme suit (seulement les parties pertinentes)

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

En principe, il s'agit d'une opération comme : yourReference.getClass - qui échouerait si votreRéférence est null .

Les choses ont changé dans jdk-9 où le même code se compile en tant que

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

Ou, en gros Objects.requireNotNull (yourReference)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X