Pourquoi utiliser Objects.requireNonNull ()?


214

J'ai noté que de nombreuses méthodes Java 8 dans Oracle JDK utilisent Objects.requireNonNull(), ce qui déclenche en interne NullPointerExceptionsi l'objet (argument) donné l'est null.

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

Mais NullPointerExceptionsera jeté de toute façon si un nullobjet est déréférencé. Alors, pourquoi devrait-on faire ce check and throw nul supplémentaire NullPointerException?

Une réponse évidente (ou avantage) est qu'elle rend le code plus lisible et je suis d'accord. Je suis désireux de connaître d'autres raisons d'utiliser Objects.requireNonNull()au début de la méthode.




1
Cette approche de la vérification des arguments vous permet de tricher lorsque vous écrivez des tests unitaires. Spring a également des utilitaires comme ça (voir docs.spring.io/spring/docs/current/javadoc-api/org/… ). Si vous avez un "si" et souhaitez 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 branchement, donc un seul passage d'un test unitaire vous permettra d'obtenir une couverture à 100% :-)
xorcus

Réponses:


214

Parce que vous pouvez rendre les choses explicites 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");

Vous savez maintenant :

  • lorsqu'un objet Foo a été créé avec succès à l'aidenew()
  • alors son champ de barre est garanti non nul.

Comparez cela à: vous créez un objet Foo aujourd'hui, et demain vous invoquez une méthode qui utilise ce champ et lance. 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 le moment où l'exception sera levée. Et la plupart du temps, vous voulez échouer le plus vite possible !

Les principaux avantages sont:

  • comme dit, comportement contrôlé
  • 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 journaux / traces vous disent ce qui ne va pas!
  • et comme indiqué ci-dessus: la véritable puissance de cette idée se déploie en conjonction avec les champs finaux . Parce que maintenant, tout autre code de votre classe peut supposer que ce barn'est pas nul - et donc vous n'avez pas besoin de if (bar == null)vérification dans d'autres endroits!

23
Vous pouvez rendre votre code plus compact en écrivantthis.bar = Objects.requireNonNull(bar, "bar must not be null");
Kirill Rakhman

3
@KirillRakhman Enseigner à GhostCat quelque chose de cool que je ne connaissais pas -> gagner un billet à la loterie GhostCat upvote.
GhostCat

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

1
Laquelle était la première, et pourquoi les gens "de la langue" devraient-ils suivre les auteurs d' une bibliothèque?! Pourquoi la goyave ne suit-elle pas le comportement de Java lang?!
GhostCat

1
Le this.bar = Objects.requireNonNull(bar, "bar must not be null");est correct dans les constructeurs, mais potentiellement dangereux dans d'autres méthodes si deux ou plusieurs variables sont définies dans la même méthode. Exemple: talkwards.com/2018/11/03/…
Erk

100

Rapide

Le code devrait planter dès que possible. Il ne doit pas faire la moitié du travail, puis déréférencer la valeur nulle et planter, puis laisser la moitié du travail effectué, ce qui rend le système dans un état non valide.

Ceci est communément appelé «échec précoce» ou «échec rapide» .


40

Mais 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 null supplémentaire et lever 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, une fois que votre code a déjà effectué certains effets secondaires
  • La référence ne peut pas du tout être déréférencée dans cette méthode
    • Il pourrait être passé à un code complètement différent (c.-à-d. Que la cause et l'erreur sont très éloignées dans l'espace de code)
    • Il pourrait être utilisé beaucoup plus tard (c.-à-d. Que la cause et l'erreur sont très éloignées dans le temps)
  • Il peut être utilisé quelque part où une référence nulle est valide, mais a un effet inattendu

.NET améliore cela 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 ce paramètre). J'aimerais que Java fasse la même chose, mais même avec juste un NullPointerException, il est encore plus facile de corriger le code si l'erreur est lancée au plus tôt à laquelle elle peut être détectée.


13

Utiliser requireNonNull()comme premières instructions dans une méthode permet d'identifier immédiatement / 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 / contrat. Passer un nullobjet à une autre méthode peut en effet provoquer une exception à la fois mais la cause du problème peut être plus compliquée à comprendre car l'exception sera lancée dans une invocation spécifique sur l' nullobjet qui peut être beaucoup plus loin.


Voici un exemple concret et réel qui montre pourquoi nous devons favoriser l'échec rapide en général et plus particulièrement en utilisant Object.requireNonNull()ou de toute manière pour effectuer une vérification non nulle sur des paramètres conçus pour ne pas l'être null.

Supposons une Dictionaryclasse qui compose un LookupServiceet un Listde Stringreprésenter les mots contenus dans. Ces champs sont conçus pour ne pas l'être nullet l'un d'eux est passé dans le Dictionary constructeur.

Supposons maintenant une "mauvaise" implémentation de Dictionarysans nullvérification dans l'entrée de 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);
    }
}

Maintenant, appelons le Dictionaryconstructeur avec une nullréférence pour le wordsparamètre:

Dictionary dictionary = new Dictionary(null); 

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

La JVM jette le NPE à cette déclaration:

return words.get(0).contains(userData); 
Exception dans le thread "main" java.lang.NullPointerException
    sur LookupService.isFirstElement (LookupService.java:5)
    à Dictionary.isFirstElement (Dictionary.java:15)
    sur Dictionary.main (Dictionary.java:22)

L'exception est déclenchée dans la LookupServiceclasse alors que son origine est bien antérieure (le Dictionaryconstructeur). Cela rend l'analyse globale des problèmes beaucoup moins évidente.
Est-ce words null? Est-ce words.get(0) null? Tous 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 qui 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'aviez pas 2 couches mais 4 ou 5 couches d'appels d'objets avant celaLookupService ? (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 (un manque de contrôle nul) devient un problème complexe?
Parce que nous avons autorisé le bug / manque initial identifiable sur une fuite de composant spécifique sur les composants inférieurs.
Imaginez que ce être détectées? Le problème serait encore plus complexe à analyser. LookupServicenull

La façon de favoriser est donc:

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

De cette façon, pas de maux 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 dans le thread "main" java.lang.NullPointerException
    sur java.util.Objects.requireNonNull (Objects.java:203)
    sur com.Dictionary. (Dictionary.java:15)
    à 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.


Hou la la! Grande explication.
Jonathas Nascimento

2

En remarque, cet échec rapide avant a Object#requireNotNullété implémenté légèrement différent avant java-9 dans certaines des classes jre elles-mêmes. Supposons que le cas:

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

Dans java-8, cela se compile comme (uniquement les parties pertinentes)

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

Fondamentalement, une opération comme: yourReference.getClass- qui échouerait si votre référence est null.

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

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

Ou en gros Objects.requireNotNull (yourReference)


1

L'utilisation de base consiste à vérifier et à lancer NullPointerException immédiatement.

Une meilleure alternative (raccourci) pour répondre à la même exigence est l' annotation @NonNull par lombok.


1
L'annotation ne remplace pas la méthode Objects, elles fonctionnent ensemble. Vous ne pouvez pas dire @ NonNull x = mightBeNull, vous diriez @ NonNull x = Objects.requireNonNull (mightBeNull, "inconceivable!");
Bill K

@BillK désolé, je ne vous ai pas
Supun Wijerathne

Je disais simplement que l'annotation Nonnull fonctionne avec requireNonNull, ce n'est pas une alternative, mais ils fonctionnent assez bien ensemble.
Bill K

mettre en œuvre la nature de l'échec rapide, c'est un droit alternatif? Oui, je suis d'accord que ce n'est pas une alternative pour la mission.
Supun Wijerathne

Je suppose que j'appellerais requireNonNull () une "conversion" de @ Nullable à @ NonNull. Si vous n'utilisez pas les annotations, la méthode n'est pas vraiment très intéressante (car elle ne fait que lancer un NPE comme le code qu'elle protège) - bien qu'elle montre assez clairement votre intention.
Bill K

1

Une exception de pointeur nul est levée lorsque vous accédez à un membre d'un objet qui se trouve nullà un stade ultérieur. Objects.requireNonNull()vérifie immédiatement la valeur et lève l'exception instantanément sans avancer.


0

Je pense qu'il devrait être utilisé dans les constructeurs de copie et dans d'autres cas comme DI dont le paramètre d'entrée est un objet, vous devriez vérifier si le paramètre est nul. Dans de telles circonstances, vous pouvez facilement utiliser cette méthode statique.


0

Dans le contexte des extensions du compilateur qui implémentent la vérification de la nullité (par exemple: uber / NullAway ),Objects.requireNonNull devrait être utilisé avec parcimonie dans les cas où vous avez un champ nullable que vous savez par hasard n'est pas nul à un certain point de votre code.

De cette façon, il existe deux usages principaux:

  • Validation

    • déjà couvert par d'autres réponses ici
    • vérification de l'exécution avec surcharge et NPE potentiel
  • Marquage de nullité (passage de @Nullable à @Nonnull)

    • utilisation minimale de la vérification à l'exécution en faveur de la vérification à la compilation
    • ne fonctionne que lorsque les annotations sont correctes (appliquées par le compilateur)

Exemple d'utilisation du marquage Nullability:

@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

En plus de toutes les bonnes réponses:

Nous l'utilisons dans des flux réactifs. Habituellement, le résultatNullpointerException s sont enveloppés dans d'autres exceptions en fonction de leur occurrence dans le flux. Par conséquent, nous pouvons plus tard décider facilement comment gérer l'erreur.

Juste un exemple: imaginez que vous avez

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

parseAndValidateenveloppe le NullPointerExceptionde requireNonNulldans un ParsingException.

Vous pouvez maintenant décider, par exemple, quand réessayer ou non:

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

Sans la vérification, l'exception NullPointerException se produira dans la saveméthode, ce qui entraînera des tentatives sans fin. Même en valeur, imaginez un abonnement longue durée, ajoutant

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

Maintenant, RetryExhaustExceptioncela tuera votre abonnement.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.