Éviter les instructions! = Null


4017

J'utilise object != nullbeaucoup pour éviter NullPointerException.

Y a-t-il une bonne alternative à cela?

Par exemple, j'utilise souvent:

if (someobject != null) {
    someobject.doCalc();
}

Cela recherche un NullPointerExceptionpour l' someobjectobjet dans l'extrait ci-dessus.

Notez que la réponse acceptée peut être obsolète, voir https://stackoverflow.com/a/2386013/12943 pour une approche plus récente.


120
@Shervin Encouraging nulls rend le code moins compréhensible et moins fiable.
Tom Hawtin - tackline

44
Les opérateurs Elvis ont été proposés mais il semble que ce ne sera pas en Java 7 . Dommage, ?. ?: et? [] sont des gains de temps incroyables.
Scott

72
Ne pas utiliser null est supérieur à la plupart des autres suggestions ici. Lancez des exceptions, ne renvoyez pas ou n'autorisez pas les valeurs nulles. BTW - Le mot clé 'assert' est inutile, car il est désactivé par défaut. Utilisez un mécanisme de défaillance toujours activé
ianpojman

13
C'est une des raisons pour lesquelles j'utilise maintenant Scala. À Scala, tout n'est pas nul. Si vous voulez autoriser à passer ou à renvoyer "rien", alors vous devez utiliser expressément l'option [T] au lieu de simplement l'argument T als ou le type de retour.
Thekwasti

11
@thSoft En effet, le Optiontype génial de Scala est entaché par la réticence du langage à contrôler ou à interdire null. J'ai mentionné cela sur hackernews et j'ai été rétrogradé et j'ai dit que "personne n'utilise null dans Scala de toute façon". Devinez quoi, je trouve déjà des null dans Scala écrit par un collègue. Oui, ils "le font mal" et doivent être éduqués à ce sujet, mais le fait demeure que le système de type de la langue devrait me protéger de cela et ce n'est pas le cas :(
Andres F.

Réponses:


2644

Pour moi, cela ressemble à un problème assez courant auquel les développeurs juniors à intermédiaires ont tendance à faire face à un moment donné: ils ne connaissent pas ou ne font pas confiance aux contrats auxquels ils participent et vérifient de manière défensive les null. De plus, lors de l'écriture de leur propre code, ils ont tendance à compter sur le retour de null pour indiquer quelque chose, ce qui oblige l'appelant à vérifier les null.

Pour le dire autrement, il y a deux cas où la vérification nulle apparaît:

  1. Lorsque null est une réponse valable en termes de contrat; et

  2. Où ce n'est pas une réponse valide.

(2) est facile. Utilisez des assertinstructions (assertions) ou autorisez l'échec (par exemple, NullPointerException ). Les assertions sont une fonctionnalité Java très sous-utilisée qui a été ajoutée dans 1.4. La syntaxe est:

assert <condition>

ou

assert <condition> : <object>

<condition>est une expression booléenne et <object>est un objet dont toString()la sortie de la méthode sera incluse dans l'erreur.

Une assertinstruction renvoie un Error( AssertionError) si la condition n'est pas vraie. Par défaut, Java ignore les assertions. Vous pouvez activer les assertions en transmettant l'option -eaà la JVM. Vous pouvez activer et désactiver les assertions pour des classes et des packages individuels. Cela signifie que vous pouvez valider le code avec les assertions lors du développement et des tests et les désactiver dans un environnement de production, bien que mes tests n'aient montré pratiquement aucun impact sur les performances des assertions.

Ne pas utiliser d'assertions dans ce cas est correct car le code échouera, ce qui se produira si vous utilisez des assertions. La seule différence est qu'avec les assertions, cela pourrait arriver plus tôt, d'une manière plus significative et éventuellement avec des informations supplémentaires, ce qui pourrait vous aider à comprendre pourquoi cela s'est produit si vous ne vous y attendiez pas.

(1) est un peu plus difficile. Si vous n'avez aucun contrôle sur le code que vous appelez, vous êtes bloqué. Si null est une réponse valide, vous devez la vérifier.

Si c'est du code que vous contrôlez cependant (et c'est souvent le cas), alors c'est une autre histoire. Évitez d'utiliser des valeurs nulles comme réponse. Avec les méthodes qui retournent des collections, c'est facile: renvoyez des collections vides (ou tableaux) au lieu de null à peu près tout le temps.

Avec des non-collections, cela pourrait être plus difficile. Considérez ceci comme un exemple: si vous avez ces interfaces:

public interface Action {
  void doSomething();
}

public interface Parser {
  Action findAction(String userInput);
}

où Parser prend les entrées utilisateur brutes et trouve quelque chose à faire, peut-être si vous implémentez une interface de ligne de commande pour quelque chose. Vous pouvez maintenant rendre le contrat qu'il renvoie nul s'il n'y a aucune action appropriée. Cela mène à la vérification nulle dont vous parlez.

Une solution alternative consiste à ne jamais retourner null et à utiliser à la place le modèle Null Object :

public class MyParser implements Parser {
  private static Action DO_NOTHING = new Action() {
    public void doSomething() { /* do nothing */ }
  };

  public Action findAction(String userInput) {
    // ...
    if ( /* we can't find any actions */ ) {
      return DO_NOTHING;
    }
  }
}

Comparer:

Parser parser = ParserFactory.getParser();
if (parser == null) {
  // now what?
  // this would be an example of where null isn't (or shouldn't be) a valid response
}
Action action = parser.findAction(someInput);
if (action == null) {
  // do nothing
} else {
  action.doSomething();
}

à

ParserFactory.getParser().findAction(someInput).doSomething();

ce qui est une bien meilleure conception car elle conduit à un code plus concis.

Cela dit, il est peut-être tout à fait approprié que la méthode findAction () lève une exception avec un message d'erreur significatif - en particulier dans ce cas où vous comptez sur une entrée utilisateur. Il serait beaucoup mieux pour la méthode findAction de lever une exception que pour la méthode appelante de sauter avec une simple NullPointerException sans explication.

try {
    ParserFactory.getParser().findAction(someInput).doSomething();
} catch(ActionNotFoundException anfe) {
    userConsole.err(anfe.getMessage());
}

Ou si vous pensez que le mécanisme try / catch est trop laid, plutôt que Ne rien faire, votre action par défaut devrait fournir des commentaires à l'utilisateur.

public Action findAction(final String userInput) {
    /* Code to return requested Action if found */
    return new Action() {
        public void doSomething() {
            userConsole.err("Action not found: " + userInput);
        }
    }
}

631
Je ne suis pas d'accord avec vos déclarations concernant l'action DO_NOTHING. Si la méthode d'action find ne peut pas trouver une action, alors retourner null est la bonne chose à faire. Vous avez "trouvé" une action dans votre code qui n'est pas vraiment trouvée, ce qui viole le principe de la méthode, pour trouver une action utilisable.
MetroidFan2002

266
Je suis d'accord que null est surutilisé en Java, en particulier avec les listes. Donc, de nombreux API seraient mieux s'ils retournent une liste / tableau / collection vide au lieu de null. Plusieurs fois, null est utilisé lorsqu'une exception doit être levée à la place. Une exception doit être levée si l'analyseur ne peut pas analyser.
Laplie Anderson

92
Le dernier exemple ici est, IIRC, le modèle de conception Null Object.
Steven Evers

62
@Cshah (et MetroidFan2002). C'est simple, mettez ça dans le contrat et alors il est clair qu'une action retournée non trouvée ne fera rien. S'il s'agit d'informations importantes pour l'appelant, fournissez un moyen de découvrir qu'il s'agissait d'une action non trouvée (c.-à-d. Fournissez une méthode pour vérifier si le résultat était l'objet DO_NOTHING). Alternativement, si une action doit normalement être trouvée, vous ne devez toujours pas retourner null mais au lieu de cela lever une exception indiquant spécifiquement cette condition - cela se traduit toujours par un meilleur code. Si vous le souhaitez, fournissez une méthode distincte renvoyant un booléen pour vérifier si une action existe.
Kevin Brock

35
Concise n'est pas synonyme de code de qualité. Je suis désolé que tu le penses. Votre code cache des situations où une erreur serait avantageuse.
gshauger

636

Si vous utilisez (ou prévoyez d'utiliser) un IDE Java comme JetBrains IntelliJ IDEA , Eclipse ou Netbeans ou un outil comme findbugs, vous pouvez utiliser des annotations pour résoudre ce problème.

Fondamentalement, vous avez @Nullableet @NotNull.

Vous pouvez utiliser dans la méthode et les paramètres, comme ceci:

@NotNull public static String helloWorld() {
    return "Hello World";
}

ou

@Nullable public static String helloWorld() {
    return "Hello World";
}

Le deuxième exemple ne se compilera pas (dans IntelliJ IDEA).

Lorsque vous utilisez la première helloWorld()fonction dans un autre morceau de code:

public static void main(String[] args)
{
    String result = helloWorld();
    if(result != null) {
        System.out.println(result);
    }
}

Maintenant, le compilateur IntelliJ IDEA vous dira que la vérification est inutile, car la helloWorld()fonction ne reviendra nulljamais.

Utilisation d'un paramètre

void someMethod(@NotNull someParameter) { }

si vous écrivez quelque chose comme:

someMethod(null);

Cela ne se compilera pas.

Dernier exemple utilisant @Nullable

@Nullable iWantToDestroyEverything() { return null; }

Ce faisant

iWantToDestroyEverything().something();

Et vous pouvez être sûr que cela n'arrivera pas. :)

C'est un bon moyen de laisser le compilateur vérifier quelque chose de plus qu'il ne le fait habituellement et de faire en sorte que vos contrats soient plus forts. Malheureusement, il n'est pas pris en charge par tous les compilateurs.

Dans IntelliJ IDEA 10.5 et versions ultérieures, ils ont ajouté la prise en charge de toutes les autres @Nullable @NotNullimplémentations.

Voir l'article de blog Annotations @ Nullable / @ NotNull plus flexibles et configurables .


120
@NotNull, @NullableEt d' autres annotations font partie de l' état NULL JSR 305 . Vous pouvez également les utiliser pour détecter des problèmes potentiels avec des outils comme FindBugs .
Jacek S

32
Je trouve étrangement ennuyeux que les interfaces @NotNull& @Nullablevivent dans le package com.sun.istack.internal. (Je suppose que j'associe com.sun aux avertissements concernant l'utilisation d'une API propriétaire.)
Jonik

20
La portabilité du code devient nulle avec Jetbrains.J'y penserais à deux fois (carré) avant de lier au niveau de l'ide.Comme Jacek S a dit qu'ils faisaient partie de JSR de toute façon que je pensais être JSR303 d'ailleurs.
Java Ka Baby

10
Je ne pense vraiment pas que l'utilisation d'un compilateur personnalisé soit une solution viable à ce problème.
Shivan Dragon

64
La bonne chose à propos des annotations, qui sont @NotNullet @Nullablesont qu'elles se dégradent bien lorsque le code source est construit par un système qui ne les comprend pas. Ainsi, en effet, l'argument selon lequel le code n'est pas portable peut être invalide - si vous utilisez un système qui prend en charge et comprend ces annotations, vous obtenez l'avantage supplémentaire d'une vérification d'erreur plus stricte, sinon vous en obtenez moins mais votre code devrait quand même build fine, et la qualité de votre programme en cours d'exécution est LA MÊME, car ces annotations n'ont pas été appliquées lors de l'exécution de toute façon. De plus, tous les compilateurs sont personnalisés ;-)
amn

322

Si les valeurs nulles ne sont pas autorisées

Si votre méthode est appelée en externe, commencez par quelque chose comme ceci:

public void method(Object object) {
  if (object == null) {
    throw new IllegalArgumentException("...");
  }

Ensuite, dans le reste de cette méthode, vous saurez que ce objectn'est pas nul.

S'il s'agit d'une méthode interne (ne faisant pas partie d'une API), documentez simplement qu'elle ne peut pas être nulle, et c'est tout.

Exemple:

public String getFirst3Chars(String text) {
  return text.subString(0, 3);
}

Cependant, si votre méthode transmet simplement la valeur et que la méthode suivante la transmet, etc., cela peut devenir problématique. Dans ce cas, vous pouvez vérifier l'argument comme ci-dessus.

Si null est autorisé

Cela dépend vraiment. Si je trouve que je fais souvent quelque chose comme ça:

if (object == null) {
  // something
} else {
  // something else
}

Je branche donc et fais deux choses complètement différentes. Il n'y a pas d'extrait de code laid, car j'ai vraiment besoin de faire deux choses différentes en fonction des données. Par exemple, dois-je travailler sur l'entrée ou dois-je calculer une bonne valeur par défaut?


Il est en fait rare que j'utilise l'idiome " if (object != null && ...".

Il peut être plus facile de vous donner des exemples si vous montrez des exemples où vous utilisez généralement l'idiome.


94
Quel est l'intérêt de lancer IllegalArgumentException? Je pense que NullPointerException serait plus clair, et cela serait également levé si vous ne faites pas la vérification nulle vous-même. J'utiliserais soit assert, soit rien du tout.
Axel

23
Il est peu probable que toute autre valeur que null soit acceptable. Vous pourriez avoir IllegalArgumentException, OutOfRageException etc. etc. Parfois, cela a du sens. D'autres fois, vous finissez par créer un grand nombre de classes d'exception qui n'ajoutent aucune valeur, puis vous utilisez simplement IllegalArgumentException. Il n'est pas logique d'avoir une exception pour null-input et une autre pour tout le reste.
myplacedk

7
Oui, j'accepte le principe de l'échec rapide, mais dans l'exemple donné ci-dessus, la valeur n'est pas transmise, mais c'est l'objet sur lequel une méthode doit être appelée. Donc, il échoue aussi rapidement, et l'ajout d'une vérification nulle juste pour lever une exception qui serait de toute façon levée au même moment et au même endroit ne semble pas rendre le débogage plus facile.
Axel

8
Un trou de sécurité? Le JDK est plein de code comme celui-ci. Si vous ne voulez pas que l'utilisateur voit les traces de pile, désactivez-les simplement. Personne n'a laissé entendre que le comportement n'est pas documenté. MySQL est écrit en C où le déréférencement de pointeurs nuls est un comportement indéfini, rien de tel que de lever une exception.
fgb

8
throw new IllegalArgumentException("object==null")
Thorbjørn Ravn Andersen

238

Wow, je déteste presque ajouter une autre réponse lorsque nous avons 57 façons différentes de recommander le NullObject pattern, mais je pense que certaines personnes intéressées par cette question aimeraient savoir qu'il y a une proposition sur la table pour Java 7 d'ajouter "null-safe" manipulation " - une syntaxe simplifiée pour la logique if-not-equal-null.

L'exemple donné par Alex Miller ressemble à ceci:

public String getPostcode(Person person) {  
  return person?.getAddress()?.getPostcode();  
}  

Le ?.moyen ne dé-référence l'identifiant gauche que s'il n'est pas nul, sinon évaluez le reste de l'expression comme null. Certaines personnes, comme le membre de Java Posse, Dick Wall, et les électeurs de Devoxx aiment vraiment cette proposition, mais il y a aussi une opposition, au motif qu'elle encouragera en fait une plus grande utilisation de la nullvaleur sentinelle.


Mise à jour: une proposition officielle pour un opérateur null-safe dans Java 7 a été soumise sous Project Coin. La syntaxe est un peu différente de l'exemple ci-dessus, mais c'est la même notion.


Mise à jour: la proposition d'opérateur null-safe n'est pas entrée dans Project Coin. Vous ne verrez donc pas cette syntaxe dans Java 7.


20
Je pense que cela est faux. Il devrait y avoir un moyen de spécifier qu'une variable donnée est TOUJOURS non nulle.
Thorbjørn Ravn Andersen

5
Mise à jour: la proposition ne fera pas Java7. Voir blogs.sun.com/darcy/entry/project_coin_final_five .
Boris Terzic

9
Idée intéressante mais le choix de la syntaxe est absurde; Je ne veux pas d'une base de code pleine de points d'interrogation épinglée dans chaque joint.
Rob

7
Cet opérateur existe dans Groovy , donc ceux qui veulent l'utiliser l'ont toujours en option.
Muhd

8
C'est l'idée la plus ingénieuse que j'ai vue. Il devrait être ajouté à chaque langage sensible de la syntaxe C. Je préfère «épingler des points d'interrogation» partout que faire défiler l'écran de lignes ou esquiver des «clauses de garde» toute la journée.
Victor

196

Si les valeurs non définies ne sont pas autorisées:

Vous pouvez configurer votre IDE pour vous avertir d'un éventuel déréférencement nul. Par exemple, dans Eclipse, voir Préférences> Java> Compilateur> Erreurs / Avertissements / Analyse nulle .

Si des valeurs non définies sont autorisées:

Si vous souhaitez définir une nouvelle API où les valeurs non définies ont un sens , utilisez le modèle d'option (peut être familier des langages fonctionnels). Il présente les avantages suivants:

  • Il est indiqué explicitement dans l'API si une entrée ou une sortie existe ou non.
  • Le compilateur vous oblige à gérer le cas "non défini".
  • L'option est une monade , il n'y a donc pas besoin de vérification nulle verbeuse, utilisez simplement map / foreach / getOrElse ou un combinateur similaire pour utiliser la valeur en toute sécurité (exemple) .

Java 8 a une Optionalclasse intégrée (recommandée); pour les versions antérieures, il existe des alternatives bibliothèque, par exemple goyave « s Optionalou FunctionalJava d » Option. Mais comme de nombreux modèles de style fonctionnel, l'utilisation d'Option en Java (même 8) donne lieu à un certain nombre de références, que vous pouvez réduire en utilisant un langage JVM moins verbeux, par exemple Scala ou Xtend.

Si vous devez gérer une API qui peut renvoyer des valeurs nulles , vous ne pouvez pas faire grand-chose en Java. Xtend et Groovy ont l' opérateur Elvis ?: et l' opérateur de déréférence null-safe ?. , mais notez que cela retourne null dans le cas d'une référence null, donc il "diffère" juste le traitement correct de null.


22
En effet, le modèle Option est génial. Certains équivalents Java existent. La goyave contient une version limitée de cette option appelée facultative qui laisse de côté la plupart des éléments fonctionnels. Dans Haskell, ce modèle est appelé Peut-être.
Ben Hardy

9
Un cours optionnel sera disponible dans Java 8
Pierre Henry

1
... et il n'a pas (encore) de carte ni de flatMap: download.java.net/jdk8/docs/api/java/util/Optional.html
thSoft

3
Le modèle facultatif ne résout rien; au lieu d'un objet potentiellement nul, vous en avez maintenant deux.
Boann

1
@Boann, si utilisé avec précaution, vous résolvez tous vos problèmes de NPE. Sinon, je suppose qu'il y a un problème "d'utilisation".
Louis F.12

189

Seulement pour cette situation -

Ne pas vérifier si une variable est nulle avant d'invoquer une méthode equals (un exemple de comparaison de chaîne ci-dessous):

if ( foo.equals("bar") ) {
 // ...
}

entraînera un NullPointerExceptionsi foon'existe pas.

Vous pouvez éviter cela si vous comparez les vôtres Stringcomme ceci:

if ( "bar".equals(foo) ) {
 // ...
}

42
Je suis d'accord - seulement dans cette situation. Je ne supporte pas les programmeurs qui ont pris cela au niveau inutile suivant et écrivent si (null! = MyVar) ... me semble tout simplement laid et ne sert à rien!
Alex Worden,

20
Il s'agit d'un exemple particulier, probablement le plus utilisé, d'une bonne pratique générale: si vous le savez, faites-le toujours <object that you know that is not null>.equals(<object that might be null>);. Cela fonctionne pour d'autres méthodes que equalssi vous connaissez le contrat et que ces méthodes peuvent gérer les nullparamètres.
Stef

9
C'est le premier exemple que j'ai vu des conditions de Yoda qui a du sens
Erin Drummond

6
NullPointerExceptions sont levées pour une raison. Ils sont lancés car un objet est nul là où il ne devrait pas être. C'est le travail des programmeurs de CORRIGER cela, pas de CACHER le problème.
Oliver Watkins

1
Try-Catch-DoNothing ne cache pas le problème, c'est une pratique valable pour contourner le sucre manquant dans la langue.
echox

167

Avec Java 8 vient la nouvelle java.util.Optionalclasse qui résout sans doute une partie du problème. On peut au moins dire que cela améliore la lisibilité du code et, dans le cas des API publiques, rendre le contrat de l'API plus clair pour le développeur client.

Ils fonctionnent comme ça:

Un objet facultatif pour un type donné ( Fruit) est créé comme type de retour d'une méthode. Il peut être vide ou contenir un Fruitobjet:

public static Optional<Fruit> find(String name, List<Fruit> fruits) {
   for (Fruit fruit : fruits) {
      if (fruit.getName().equals(name)) {
         return Optional.of(fruit);
      }
   }
   return Optional.empty();
}

Regardez maintenant ce code où nous recherchons une liste de Fruit( fruits) pour une instance de Fruit donnée:

Optional<Fruit> found = find("lemon", fruits);
if (found.isPresent()) {
   Fruit fruit = found.get();
   String name = fruit.getName();
}

Vous pouvez utiliser l' map()opérateur pour effectuer un calcul sur - ou extraire une valeur - d'un objet facultatif. orElse()vous permet de remplacer les valeurs manquantes.

String nameOrNull = find("lemon", fruits)
    .map(f -> f.getName())
    .orElse("empty-name");

Bien sûr, la vérification de la valeur nulle / vide est toujours nécessaire, mais au moins le développeur est conscient que la valeur peut être vide et le risque d'oublier de vérifier est limité.

Dans une API construite à partir de zéro en utilisant Optionalchaque fois qu'une valeur de retour peut être vide, et en renvoyant un objet simple uniquement lorsqu'il ne peut pas l'être null(convention), le code client peut abandonner les vérifications nulles sur les valeurs de retour d'objet simples ...

Bien sûr, Optionalpourrait également être utilisé comme argument de méthode, peut-être un meilleur moyen d'indiquer des arguments facultatifs que 5 ou 10 méthodes de surcharge dans certains cas.

Optionalpropose d'autres méthodes pratiques, telles que celles orElsequi permettent l'utilisation d'une valeur par défaut, et ifPresentqui fonctionnent avec les expressions lambda .

Je vous invite à lire cet article (ma principale source d'écriture de cette réponse) dans lequel la NullPointerExceptionproblématique (et en général le pointeur nul) ainsi que la solution (partielle) apportée Optionalsont bien expliquées: Java Optional Objects .


11
La goyave de Google a une implémention facultative pour Java 6+.
Bradley Gottfried

13
Il est très important de souligner que l'utilisation d'Optional uniquement avec ifPresent () n'ajoute pas beaucoup de valeur au-dessus de la vérification nulle normale. Sa valeur fondamentale est qu'il s'agit d'une monade qui peut être utilisée dans les chaînes de fonctions de map / flapMap, qui obtient des résultats similaires à l'opérateur Elvis dans Groovy mentionné ailleurs. Même sans cette utilisation, je trouve que la syntaxe orElse / orElseThrow est également très utile.
Cornel Masson,


4
Pourquoi les gens ont tendance à faire cela if(optional.isPresent()){ optional.get(); }au lieu deoptional.ifPresent(o -> { ...})
Satyendra Kumar

1
Donc, à part les conseils contractuels de l'API, il s'agit vraiment de répondre aux programmeurs fonctionnels qui aiment sans cesse les méthodes de chaînage.
écraser

125

Selon le type d'objets que vous vérifiez, vous pourrez peut-être utiliser certaines des classes dans les collections apache commons telles que: collections apache commons lang et apache commons

Exemple:

String foo;
...
if( StringUtils.isBlank( foo ) ) {
   ///do something
}

ou (selon ce que vous devez vérifier):

String foo;
...
if( StringUtils.isEmpty( foo ) ) {
   ///do something
}

La classe StringUtils n'est que l'une des nombreuses; il y a pas mal de bonnes classes dans les communs qui font des manipulations sûres nulles.

Voici un exemple de la façon dont vous pouvez utiliser la vallidation nulle dans JAVA lorsque vous incluez la bibliothèque apache (commons-lang-2.4.jar)

public DOCUMENT read(String xml, ValidationEventHandler validationEventHandler) {
    Validate.notNull(validationEventHandler,"ValidationHandler not Injected");
    return read(new StringReader(xml), true, validationEventHandler);
}

Et si vous utilisez Spring, Spring a également la même fonctionnalité dans son package, voir bibliothèque (spring-2.4.6.jar)

Exemple d'utilisation de cette classe statique à partir du printemps (org.springframework.util.Assert)

Assert.notNull(validationEventHandler,"ValidationHandler not Injected");

5
Vous pouvez également utiliser la version plus générique d'Apache Commons, très utile au début des méthodes pour vérifier les paramètres que je trouve. Validate.notNull (object, "object must not be null"); commons.apache.org/lang/apidocs/org/apache/commons/lang/...
monojohnny

@monojohnny Validate utilise-t-il les instructions Assert dans?. Je demande cela car Assert peut être activé / désactivé sur JVM et il est suggéré de ne pas l'utiliser en production.
Kurapika

Ne pensez pas - je crois que cela déclenche juste une exception RuntimeException si la validation échoue
monojohnny

96
  • Si vous considérez qu'un objet ne doit pas être nul (ou s'il s'agit d'un bogue), utilisez une assertion.
  • Si votre méthode n'accepte pas les paramètres nuls, dites-le dans le javadoc et utilisez une assertion.

Vous devez vérifier l'objet! = Null uniquement si vous voulez gérer le cas où l'objet peut être nul ...

Il est proposé d'ajouter de nouvelles annotations dans Java7 pour aider avec les paramètres null / notnull: http://tech.puredanger.com/java7/#jsr308



91

Je suis fan du code "fail fast". Demandez-vous - faites-vous quelque chose d'utile dans le cas où le paramètre est nul? Si vous n'avez pas de réponse claire à ce que votre code devrait faire dans ce cas ... C'est-à-dire qu'il ne devrait jamais être nul en premier lieu, alors ignorez-le et autorisez une NullPointerException à être levée. Le code appelant aura autant de sens pour un NPE que pour une exception IllegalArgumentException, mais il sera plus facile pour le développeur de déboguer et de comprendre ce qui s'est passé si un NPE est lancé plutôt que votre code tentant d'exécuter une autre contingence inattendue logique - ce qui entraîne finalement l'échec de l'application de toute façon.


2
mieux utiliser les assertions, c'est-à-dire Contract.notNull (abc, "abc doit être non nul, at-il échoué lors du chargement pendant xyz?"); - c'est un moyen plus compact que de faire un if (abc! = null) {throw new RuntimeException ...}
ianpojman

76

Le cadre des collections Google offre un moyen efficace et élégant de réaliser la vérification nulle.

Il existe une méthode dans une classe de bibliothèque comme celle-ci:

static <T> T checkNotNull(T e) {
   if (e == null) {
      throw new NullPointerException();
   }
   return e;
}

Et l'utilisation est (avec import static):

...
void foo(int a, Person p) {
   if (checkNotNull(p).getAge() > a) {
      ...
   }
   else {
      ...
   }
}
...

Ou dans votre exemple:

checkNotNull(someobject).doCalc();

71
mmm, quelle est la différence? p.getAge () lancerait le même NPE avec moins de surcharge et une trace de pile plus claire. Qu'est-ce que je rate?
mysomic

15
Il est préférable de lever une exception IllegalArgumentException ("e == null") dans votre exemple, car cela indique clairement qu'il s'agit d'une exception destinée au programmeur (avec suffisamment d'informations pour permettre au responsable d'identifier le problème). NullPointerExceptions doit être réservé à la JVM, car cela indique alors clairement que cela n'était pas intentionnel (et se produit généralement dans un endroit difficile à identifier)
Thorbjørn Ravn Andersen

6
Cela fait maintenant partie de Google Guava.
Steven Benitez

32
Ça me fait trop d'ingénierie. Laissez simplement la JVM lancer un NPE et n'encombrez pas votre code avec ces indésirables.
Alex Worden

5
Je l'aime et j'ouvre la plupart des méthodes et des constructeurs avec des vérifications explicites sur les arguments; s'il y a une erreur, les méthodes échouent toujours sur les premières lignes et je connais la référence incriminée sans trouver quelque chose commegetThing().getItsThing().getOtherThing().wowEncapsulationIsBroken().setLol("hi");
Cory Kendall

75

Parfois, vous avez des méthodes qui opèrent sur ses paramètres qui définissent une opération symétrique:

a.f(b); <-> b.f(a);

Si vous savez que b ne peut jamais être nul, vous pouvez simplement l'échanger. Il est très utile pour les égaux: au lieu de foo.equals("bar");faire mieux "bar".equals(foo);.


2
Mais alors vous devez supposer equals(pourrait être n'importe quelle méthode) que Null sera correctement géré. Vraiment, tout cela consiste à passer la responsabilité à quelqu'un d'autre (ou à une autre méthode).
Supericy

4
@Supericy Fondamentalement oui, mais equals(ou quelle que soit la méthode) doit vérifier de nulltoute façon. Ou déclarez explicitement que non.
Angelo Fuchs

74

Plutôt que Null Object Pattern - qui a ses utilisations - vous pourriez envisager des situations où l'objet nul est un bogue.

Lorsque l'exception est levée, examinez la trace de la pile et résolvez le bogue.


17
Le problème est qu'en général, vous perdez le contexte car NullPointerException n'indique pas QUELLE variable était nulle et vous pouvez avoir plusieurs opérations "." Sur la ligne. L'utilisation de "if (foo == null) throw new RuntimeException (" foo == null ")" vous permet d'indiquer explicitement ce qui ne va pas, donnant à votre trace de pile beaucoup plus de valeur pour ceux qui doivent y remédier.
Thorbjørn Ravn Andersen

1
Avec Andersen - j'aimerais que le système d'exceptions de Java puisse inclure le nom d'une variable sur laquelle on travaille, de sorte que NullPointerExceptions indique non seulement la ligne sur laquelle l'exception s'est produite, mais aussi le nom de la variable. Cela devrait très bien fonctionner dans un logiciel non obscurci.
fwielstra

Je n'ai pas encore réussi à le faire fonctionner, mais cela vise à résoudre exactement ce problème.
MatrixFrog

Quel est le problème? Il suffit d'attraper le NPE au niveau approprié où suffisamment de contexte est disponible, de vider les informations de contexte et de renvoyer l'exception ... c'est si facile avec Java.
user1050755

3
J'avais un professeur qui prêchait contre le chaînage d'appels de méthode. Sa théorie était que vous devriez vous méfier des chaînes d'appel qui étaient plus longues que 2 méthodes. Je ne sais pas si c'est une règle stricte, mais cela élimine définitivement la plupart des problèmes avec les traces de pile NPE.
RustyTheBoyRobot

74

Null n'est pas un «problème». Il fait partie intégrante d'un ensemble complet d' outils de modélisation. Le logiciel vise à modéliser la complexité du monde et null porte son fardeau. Null indique «Aucune donnée» ou «Inconnu» en Java et similaires. Il est donc approprié d'utiliser des valeurs nulles à ces fins. Je ne préfère pas le motif «objet nul»; Je pense que cela soulève le problème « qui gardera les gardiens ».
Si vous me demandez quel est le nom de ma petite amie, je vous dirai que je n'ai pas de petite amie. Dans le langage Java, je retournerai null. Une alternative serait de lever une exception significative pour indiquer un problème qui ne peut pas être (ou ne pas ''

  1. Pour une «question inconnue», donnez «réponse inconnue». (Assurez-vous que la valeur NULL est correcte du point de vue commercial) La vérification des arguments NULL une fois à l'intérieur d'une méthode avant utilisation évite à plusieurs appelants de les vérifier avant un appel.

    public Photo getPhotoOfThePerson(Person person) {
        if (person == null)
            return null;
        // Grabbing some resources or intensive calculation
        // using person object anyhow.
    }

    Le précédent conduit à un flux logique normal pour ne pas obtenir de photo d'une petite amie inexistante de ma photothèque.

    getPhotoOfThePerson(me.getGirlfriend())

    Et il correspond à la nouvelle API Java à venir (pour l'avenir)

    getPhotoByName(me.getGirlfriend()?.getName())

    Bien qu'il soit plutôt `` flux commercial normal '' de ne pas trouver de photo stockée dans la base de données pour une personne, j'ai utilisé des paires comme ci-dessous pour d'autres cas

    public static MyEnum parseMyEnum(String value); // throws IllegalArgumentException
    public static MyEnum parseMyEnumOrNull(String value);

    Et ne détestez pas taper <alt> + <shift> + <j>(générer javadoc dans Eclipse) et écrire trois mots supplémentaires pour votre API publique. Ce sera plus que suffisant pour tous, sauf pour ceux qui ne lisent pas la documentation.

    /**
     * @return photo or null
     */

    ou

    /**
     * @return photo, never null
     */
  2. C'est un cas plutôt théorique et dans la plupart des cas, vous devriez préférer l'API java null safe (au cas où elle serait publiée dans 10 ans), mais NullPointerExceptionc'est une sous-classe d'un Exception. C'est donc une forme Throwablequi indique les conditions qu'une application raisonnable pourrait vouloir attraper ( javadoc )! Pour utiliser le premier avantage des exceptions et séparer le code de gestion des erreurs du code «normal» ( selon les créateurs de Java ), il est approprié, comme pour moi, d'attraper NullPointerException.

    public Photo getGirlfriendPhoto() {
        try {
            return appContext.getPhotoDataSource().getPhotoByName(me.getGirlfriend().getName());
        } catch (NullPointerException e) {
            return null;
        }
    }

    Des questions pourraient se poser:

    Q. Et si getPhotoDataSource()renvoie null?
    R. Cela dépend de la logique métier. Si je ne trouve pas d'album photo, je ne vous montrerai aucune photo. Que faire si appContext n'est pas initialisé? La logique métier de cette méthode supporte cela. Si la même logique doit être plus stricte que de lancer une exception, elle fait partie de la logique métier et une vérification explicite de null doit être utilisée (cas 3). La nouvelle API Java Null-safe s'adapte mieux ici pour spécifier sélectivement ce qui implique et ce qui n'implique pas d'être initialisé pour être rapide en cas d'erreurs de programmation.

    Q. Du code redondant pourrait être exécuté et des ressources inutiles pourraient être récupérées.
    A. Cela pourrait avoir lieu si getPhotoByName()j'essayais d'ouvrir une connexion à une base de données, de créer PreparedStatementet d'utiliser enfin le nom de la personne comme paramètre SQL. L'approche pour une question inconnue donne une réponse inconnue (cas 1) fonctionne ici. Avant de récupérer des ressources, la méthode doit vérifier les paramètres et retourner un résultat «inconnu» si nécessaire.

    Q. Cette approche a une pénalité de performance en raison de l'ouverture de fermeture d'essai.
    A. Le logiciel doit être facile à comprendre et à modifier en premier lieu. Ce n'est qu'après cela que l'on pourrait penser aux performances, et seulement si nécessaire! et là où c'est nécessaire! ( source ), et bien d'autres).

    PS. Cette approche sera aussi raisonnable à utiliser que le code de gestion d'erreurs distinct du principe de code "normal" est raisonnable à utiliser à un certain endroit. Prenons l'exemple suivant:

    public SomeValue calculateSomeValueUsingSophisticatedLogic(Predicate predicate) {
        try {
            Result1 result1 = performSomeCalculation(predicate);
            Result2 result2 = performSomeOtherCalculation(result1.getSomeProperty());
            Result3 result3 = performThirdCalculation(result2.getSomeProperty());
            Result4 result4 = performLastCalculation(result3.getSomeProperty());
            return result4.getSomeProperty();
        } catch (NullPointerException e) {
            return null;
        }
    }
    
    public SomeValue calculateSomeValueUsingSophisticatedLogic(Predicate predicate) {
        SomeValue result = null;
        if (predicate != null) {
            Result1 result1 = performSomeCalculation(predicate);
            if (result1 != null && result1.getSomeProperty() != null) {
                Result2 result2 = performSomeOtherCalculation(result1.getSomeProperty());
                if (result2 != null && result2.getSomeProperty() != null) {
                    Result3 result3 = performThirdCalculation(result2.getSomeProperty());
                    if (result3 != null && result3.getSomeProperty() != null) {
                        Result4 result4 = performLastCalculation(result3.getSomeProperty());
                        if (result4 != null) {
                            result = result4.getSomeProperty();
                        }
                    }
                }
            }
        }
        return result;
    }

    PPS. Pour ceux qui souhaitent voter rapidement (et pas si rapidement pour lire la documentation), je voudrais dire que je n'ai jamais détecté d'exception de pointeur nul (NPE) dans ma vie. Mais cette possibilité a été intentionnellement conçue par les créateurs Java car NPE est une sous-classe de Exception. Nous avons un précédent dans l'histoire de Java quand ce ThreadDeathn'est Errorpas le cas car il s'agit en fait d'une erreur d'application, mais uniquement parce qu'il n'était pas destiné à être rattrapé! Combien de NPE convient pour être un Errorque ThreadDeath! Mais ce n'est pas.

  3. Cochez «Aucune donnée» uniquement si la logique métier le sous-entend.

    public void updatePersonPhoneNumber(Long personId, String phoneNumber) {
        if (personId == null)
            return;
        DataSource dataSource = appContext.getStuffDataSource();
        Person person = dataSource.getPersonById(personId);
        if (person != null) {
            person.setPhoneNumber(phoneNumber);
            dataSource.updatePerson(person);
        } else {
            Person = new Person(personId);
            person.setPhoneNumber(phoneNumber);
            dataSource.insertPerson(person);
        }
    }

    et

    public void updatePersonPhoneNumber(Long personId, String phoneNumber) {
        if (personId == null)
            return;
        DataSource dataSource = appContext.getStuffDataSource();
        Person person = dataSource.getPersonById(personId);
        if (person == null)
            throw new SomeReasonableUserException("What are you thinking about ???");
        person.setPhoneNumber(phoneNumber);
        dataSource.updatePerson(person);
    }

    Si appContext ou dataSource n'est pas initialisé, le runtime non géré NullPointerException tuera le thread actuel et sera traité par Thread.defaultUncaughtExceptionHandler (pour que vous puissiez définir et utiliser votre enregistreur préféré ou un autre mécanisme de notification). S'il n'est pas défini, ThreadGroup # uncaughtException imprime stacktrace à l'erreur système. Il faut surveiller le journal des erreurs d'application et ouvrir le problème Jira pour chaque exception non gérée qui est en fait une erreur d'application. Le programmeur devrait corriger le bogue quelque part dans les trucs d'initialisation.


6
Attraper NullPointerExceptionet retourner nullest horrible à déboguer. Vous vous retrouvez avec NPE plus tard de toute façon, et il est vraiment difficile de comprendre ce qui était initialement nul.
artbristol

23
Je voterais moins si j'avais la réputation. Non seulement null n'est pas nécessaire, c'est un trou dans le système de type. L'affectation d'un arbre à une liste est une erreur de type car les arbres ne sont pas des valeurs de type liste; selon cette même logique, l'attribution de null devrait être une erreur de type car null n'est pas une valeur de type Object, ni aucun type utile d'ailleurs. Même l'homme qui a inventé le null le considère comme son "erreur d'un milliard de dollars". La notion de "valeur qui pourrait être une valeur de type T OU rien" est son propre type et doit être représentée comme telle (par exemple Peut-être <T> ou Facultatif <T>).
Doval

2
À partir de "Peut-être <T> ou Facultatif <T>", vous devez toujours écrire du code comme if (maybeNull.hasValue()) {...}ça, alors quelle est la différence avec if (maybeNull != null)) {...}?
Mykhaylo Adamovych

1
À partir de "attraper NullPointerException et retourner null est horrible à déboguer. Vous vous retrouvez avec NPE plus tard de toute façon, et il est vraiment difficile de comprendre ce qui était à l'origine nul". Je suis tout à fait d'accord! Dans ces cas, vous devez écrire une douzaine d'instructions «if» ou lancer NPE si la logique métier implique des données en place, ou utiliser un opérateur null-safe depuis un nouveau Java. Mais il y a des cas où je ne me soucie pas de quelle étape exacte me donne la nullité. Par exemple, calculer certaines valeurs pour l'utilisateur juste avant d'afficher à l'écran lorsque vous pensez que des données pourraient être manquantes.
Mykhaylo Adamovych

3
@MykhayloAdamovych: L'avantage de Maybe<T>ou Optional<T>n'est pas dans le cas où votre Tpourrait être nul, mais dans le cas où il ne devrait jamais être nul. Si vous avez un type qui signifie explicitement "cette valeur peut être nulle - utilisez avec prudence", et que vous utilisez et renvoyez un tel type de manière cohérente, chaque fois que vous voyez un ancien simple Tdans votre code, vous pouvez supposer qu'il n'est jamais nul. (
Bien

71

Java 7 a une nouvelle java.util.Objectsclasse utilitaire sur laquelle il existe une requireNonNull()méthode. Tout cela ne fait que lancer un NullPointerExceptionsi son argument est nul, mais il nettoie un peu le code. Exemple:

Objects.requireNonNull(someObject);
someObject.doCalc();

La méthode est très utile pour vérifier juste avant une affectation dans un constructeur, où chaque utilisation de celle-ci peut enregistrer trois lignes de code:

Parent(Child child) {
   if (child == null) {
      throw new NullPointerException("child");
   }
   this.child = child;
}

devient

Parent(Child child) {
   this.child = Objects.requireNonNull(child, "child");
}

9
En fait, votre exemple constitue une surcharge de code: la première ligne est superflue car le NPE serait jeté dans la deuxième ligne. ;-)
user1050755

1
Vrai. Un meilleur exemple serait si la deuxième ligne l'était doCalc(someObject).
Stuart marque

Dépend. Si vous êtes l'auteur de doCalc (), je vous suggère de mettre la vérification dans le corps de cette méthode (si possible). Et puis vous appellerez très probablement someObject.someMethod () où là encore il n'est pas nécessaire de vérifier la valeur null. :-)
user1050755

Eh bien, si vous n'êtes pas l'auteur de doCalc(), et qu'il ne lance pas immédiatement NPE lorsqu'il est nul, vous devez vérifier pour null et lancer NPE vous-même. C'est pour ça Objects.requireNonNull().
Stuart marque

7
Ce n'est pas seulement du ballonnement de code. Mieux vaut vérifier à l'avance qu'à mi-chemin d'une méthode qui provoque des effets secondaires ou utilise le temps / l'espace.
Rob Grant

51

En fin de compte, la seule façon de résoudre complètement ce problème est d'utiliser un langage de programmation différent:

  • Dans Objective-C, vous pouvez faire l'équivalent d'invoquer une méthode nilet absolument rien ne se passera. Cela rend la plupart des contrôles nuls inutiles, mais cela peut rendre les erreurs beaucoup plus difficiles à diagnostiquer.
  • A Nice , un langage dérivé de Java, il existe deux versions de tous types: une version potentiellement nulle et une version non nulle. Vous ne pouvez appeler des méthodes que sur des types non nuls. Les types potentiellement nuls peuvent être convertis en types non nuls grâce à une vérification explicite de null. Cela permet de savoir plus facilement où les vérifications nulles sont nécessaires et où elles ne le sont pas.

Woah ... La réponse la plus correcte et elle est rejetée, où est la justice? En Java, null est toujours une valeur valide. C'est le corollaire de tout est un objet - Null est un tout (bien sûr, nous ignorons les primitives ici, mais vous avez l'idée). Personnellement, je suis favorable à l'approche adoptée par Nice, bien que nous puissions faire en sorte que les méthodes puissent être invoquées sur des types nullables et promouvoir les NPE en exceptions vérifiées. Cela devrait être fait via un commutateur de compilation, car cela casserait tout le code existant :(
CurtainDog

4
Je ne connais pas Nice, mais Kotlin implémente la même idée, avoir des types nullable et non null construits dans le système de type du langage. Beaucoup plus concis que les options ou le modèle d'objet nul.
mtsahakis

38

"Problème" commun en Java en effet.

Tout d'abord, mes réflexions à ce sujet:

Je considère qu'il est mauvais de "manger" quelque chose lorsque NULL a été passé alors que NULL n'est pas une valeur valide. Si vous ne quittez pas la méthode avec une sorte d'erreur, cela signifie que rien ne s'est mal passé dans votre méthode, ce qui n'est pas vrai. Ensuite, vous retournez probablement null dans ce cas, et dans la méthode de réception, vous vérifiez à nouveau pour null, et cela ne se termine jamais, et vous vous retrouvez avec "if! = Null", etc.

Donc, à mon humble avis, null doit être une erreur critique qui empêche toute exécution ultérieure (c'est-à-dire où null n'est pas une valeur valide).

La façon dont je résous ce problème est la suivante:

Tout d'abord, je respecte cette convention:

  1. Toutes les méthodes / API publiques vérifient toujours ses arguments pour null
  2. Toutes les méthodes privées ne vérifient pas null car ce sont des méthodes contrôlées (laissez simplement mourir avec l'exception nullpointer au cas où cela ne serait pas géré ci-dessus)
  3. Les seules autres méthodes qui ne vérifient pas la valeur null sont les méthodes utilitaires. Ils sont publics, mais si vous les appelez pour une raison quelconque, vous savez quels paramètres vous passez. C'est comme essayer de faire bouillir de l'eau dans la bouilloire sans fournir d'eau ...

Et enfin, dans le code, la première ligne de la méthode publique se présente comme suit:

ValidationUtils.getNullValidator().addParam(plans, "plans").addParam(persons, "persons").validate();

Notez que addParam () renvoie self, afin que vous puissiez ajouter plus de paramètres à vérifier.

La méthode validate()lancera vérifié ValidationExceptionsi l'un des paramètres est nul (coché ou décoché est plus un problème de conception / goût, mais mon ValidationExceptionest vérifié).

void validate() throws ValidationException;

Le message contiendra le texte suivant si, par exemple, "plans" est nul:

"Une valeur d'argument non valide est rencontrée pour le paramètre [plans] "

Comme vous pouvez le voir, la deuxième valeur de la méthode (chaîne) addParam () est nécessaire pour le message utilisateur, car vous ne pouvez pas facilement détecter le nom de variable transmise, même avec réflexion (de toute façon pas l'objet de cet article ...).

Et oui, nous savons qu'au-delà de cette ligne, nous ne rencontrerons plus de valeur nulle, nous invoquons donc en toute sécurité des méthodes sur ces objets.

De cette façon, le code est propre, facile à entretenir et lisible.


3
Absolument. Les applications qui génèrent simplement l'erreur et le plantage sont de meilleure qualité car il n'y a aucun doute lorsqu'elles ne fonctionnent pas. Les applications qui avalent les erreurs au mieux se dégradent gracieusement mais ne fonctionnent généralement pas de manière difficile à remarquer et ne sont pas corrigées. Et lorsque le problème est remarqué, ils sont beaucoup plus difficiles à déboguer.
Paul Jackson

35

Poser cette question indique que vous pourriez être intéressé par les stratégies de gestion des erreurs. L'architecte de votre équipe doit décider de la façon de traiter les erreurs. Il y a plusieurs moyens de le faire:

  1. permettre aux exceptions de se propager à travers - les attraper à la «boucle principale» ou dans une autre routine de gestion.

    • vérifier les conditions d'erreur et les gérer de manière appropriée

Bien sûr, jetez également un œil à la programmation orientée aspect - ils ont des façons intéressantes d'insérer if( o == null ) handleNull()dans votre bytecode.


35

En plus d'utiliser, assertvous pouvez utiliser les éléments suivants:

if (someobject == null) {
    // Handle null here then move on.
}

C'est légèrement mieux que:

if (someobject != null) {
    .....
    .....



    .....
}

2
Mh, pourquoi ça? S'il vous plaît, ne vous sentez pas défensif, je voudrais juste en savoir plus sur Java :)
Matthias Meid

9
@Mudu En règle générale, je préfère que l'expression dans une instruction if soit une instruction plus "positive" plutôt qu'une instruction "négative". Donc, si je voyais, if (!something) { x(); } else { y(); }je serais enclin à le refactoriser if (something) { y(); } else { x(); }(bien que l'on puisse dire que != nullc'est l'option la plus positive ...). Mais plus important encore, la partie importante du code n'est pas encapsulée à l'intérieur de {}s et vous avez un niveau de retrait de moins pour la plupart de la méthode. Je ne sais pas si c'était le raisonnement de fastcodejava mais ce serait le mien.
MatrixFrog

1
C'est aussi ce que j'ai tendance à faire .. Garde le code propre à mon avis.
Koray Tugay

34

N'utilisez jamais null. Ne le permettez pas.

Dans mes classes, la plupart des champs et des variables locales ont des valeurs par défaut non nulles, et j'ajoute des déclarations de contrat (assertions toujours actives) partout dans le code pour s'assurer que cela est appliqué (car c'est plus succinct et plus expressif que de le laisser) venir comme un NPE et ensuite avoir à résoudre le numéro de ligne, etc.).

Une fois que j'ai adopté cette pratique, j'ai remarqué que les problèmes semblaient se régler. Vous attraperiez les choses beaucoup plus tôt dans le processus de développement juste par accident et vous vous rendriez compte que vous aviez un point faible .. et plus important encore .. cela aide à résumer les préoccupations des différents modules, différents modules peuvent se `` faire confiance '' les uns les autres, et ne jonchent plus les code avecif = null else constructions!

Il s'agit d'une programmation défensive et se traduit par un code beaucoup plus propre à long terme. Nettoyez toujours les données, par exemple ici en appliquant des normes rigides, et les problèmes disparaissent.

class C {
    private final MyType mustBeSet;
    public C(MyType mything) {
       mustBeSet=Contract.notNull(mything);
    }
   private String name = "<unknown>";
   public void setName(String s) {
      name = Contract.notNull(s);
   }
}


class Contract {
    public static <T> T notNull(T t) { if (t == null) { throw new ContractException("argument must be non-null"); return t; }
}

Les contrats sont comme des mini-tests unitaires qui sont toujours en cours, même en production, et quand les choses échouent, vous savez pourquoi, plutôt qu'un NPE aléatoire, vous devez en quelque sorte comprendre.


pourquoi cela serait-il revu à la baisse? d'après mon expérience, ceci est de loin supérieur aux autres approches, j'aimerais savoir pourquoi
ianpojman

Je suis d'accord, cette approche évite les problèmes liés aux null, plutôt que de les résoudre en mouchetant les contrôles de null du code partout.
ChrisBlom

7
Le problème avec cette approche est que si le nom n'est jamais défini, il a la valeur "<inconnu>", qui se comporte comme une valeur définie. Maintenant, disons que je dois vérifier si le nom n'a jamais été défini (inconnu), je dois faire une comparaison de chaînes avec la valeur spéciale "<inconnu>".
Steve Kuo

1
Vrai bon point Steve. Ce que je fais souvent, c'est que cette valeur soit une constante, par exemple, une chaîne publique statique finale UNSET = "__ unset" ... un champ String privé = UNSET ... puis un booléen privé isSet () {return UNSET.equals (champ); }
ianpojman

À mon humble avis, il s'agit d'une implémentation de Null Object Pattern avec une implémentation de vous-même facultative (contrat). Comment se comporte-t-il sur la classe de classe de persistance? Je ne vois pas applicable dans ce cas.
Kurapika

31

Guava, une bibliothèque de base très utile de Google, possède une API agréable et utile pour éviter les null. Je trouve UsingAndAvoidingNullExplained très utile.

Comme expliqué dans le wiki:

Optional<T>est un moyen de remplacer une référence T nullable par une valeur non nulle. Un Facultatif peut soit contenir une référence T non nulle (auquel cas nous disons que la référence est "présente"), soit il ne peut rien contenir (auquel cas nous disons que la référence est "absente"). On ne dit jamais qu'il "contient null".

Usage:

Optional<Integer> possible = Optional.of(5);
possible.isPresent(); // returns true
possible.get(); // returns 5

@CodyGuldner À droite, Cody. J'ai fourni une citation pertinente du lien pour donner plus de contexte.
Murat Derya Özen

25

Il s'agit d'un problème très courant pour tous les développeurs Java. Il existe donc un support officiel dans Java 8 pour résoudre ces problèmes sans code encombré.

Java 8 a introduit java.util.Optional<T>. Il s'agit d'un conteneur qui peut contenir ou non une valeur non nulle. Java 8 a donné un moyen plus sûr de gérer un objet dont la valeur peut être nulle dans certains cas. Il est inspiré des idées de Haskell et Scala .

En un mot, la classe Optional inclut des méthodes pour traiter explicitement les cas où une valeur est présente ou absente. Cependant, l'avantage par rapport aux références nulles est que la classe Facultatif <T> vous oblige à réfléchir au cas où la valeur n'est pas présente. Par conséquent, vous pouvez empêcher les exceptions de pointeur nul involontaires.

Dans l'exemple ci-dessus, nous avons une usine de service à domicile qui renvoie une poignée à plusieurs appareils disponibles dans la maison. Mais ces services peuvent être ou ne pas être disponibles / fonctionnels; cela signifie que cela peut entraîner une exception NullPointerException. Au lieu d'ajouter un nullif condition avant d'utiliser un service, encapsulons-la dans <Service> facultatif.

EMBALLAGE À L'OPTION <T>

Considérons une méthode pour obtenir une référence d'un service d'une usine. Au lieu de renvoyer la référence de service, enveloppez-la avec Facultatif. Il permet à l'utilisateur de l'API de savoir que le service retourné peut ou non être disponible / fonctionnel, utiliser de manière défensive

public Optional<Service> getRefrigertorControl() {
      Service s = new  RefrigeratorService();
       //...
      return Optional.ofNullable(s);
   }

Comme vous le voyez, Optional.ofNullable()fournit un moyen facile d'obtenir la référence encapsulée. Il existe un autre moyen d'obtenir la référence de Facultatif, soit Optional.empty()&Optional.of() . L'une pour renvoyer un objet vide au lieu de réaccorder null et l'autre pour encapsuler un objet non nullable, respectivement.

AINSI COMMENT EXACTEMENT IL AIDE À ÉVITER UN CHÈQUE NUL?

Une fois que vous avez encapsulé un objet de référence, Optional fournit de nombreuses méthodes utiles pour appeler des méthodes sur une référence encapsulée sans NPE.

Optional ref = homeServices.getRefrigertorControl();
ref.ifPresent(HomeServices::switchItOn);

Facultatif.ifPresent appelle le consommateur donné avec une référence s'il s'agit d'une valeur non nulle. Sinon, cela ne fait rien.

@FunctionalInterface
public interface Consumer<T>

Représente une opération qui accepte un seul argument d'entrée et ne renvoie aucun résultat. Contrairement à la plupart des autres interfaces fonctionnelles, Consumer devrait fonctionner via des effets secondaires. C'est tellement propre et facile à comprendre. Dans l'exemple de code ci-dessus,HomeService.switchOn(Service) obtient invoqué si la référence de détention facultative n'est pas nulle.

Nous utilisons l'opérateur ternaire très souvent pour vérifier la condition nulle et renvoyer une valeur alternative ou une valeur par défaut. Facultatif fournit une autre façon de gérer la même condition sans vérifier null. Optional.orElse (defaultObj) renvoie defaultObj si l'Optional a une valeur nulle. Utilisons ceci dans notre exemple de code:

public static Optional<HomeServices> get() {
    service = Optional.of(service.orElse(new HomeServices()));
    return service;
}

Maintenant, HomeServices.get () fait la même chose, mais d'une meilleure façon. Il vérifie si le service est déjà initialisé ou non. Si c'est le cas, renvoyez-le ou créez un nouveau nouveau service. <T> .orElse (T) facultatif permet de renvoyer une valeur par défaut.

Enfin, voici notre NPE ainsi que le code sans vérification nul:

import java.util.Optional;
public class HomeServices {
    private static final int NOW = 0;
    private static Optional<HomeServices> service;

public static Optional<HomeServices> get() {
    service = Optional.of(service.orElse(new HomeServices()));
    return service;
}

public Optional<Service> getRefrigertorControl() {
    Service s = new  RefrigeratorService();
    //...
    return Optional.ofNullable(s);
}

public static void main(String[] args) {
    /* Get Home Services handle */
    Optional<HomeServices> homeServices = HomeServices.get();
    if(homeServices != null) {
        Optional<Service> refrigertorControl = homeServices.get().getRefrigertorControl();
        refrigertorControl.ifPresent(HomeServices::switchItOn);
    }
}

public static void switchItOn(Service s){
         //...
    }
}

Le message complet est NPE ainsi que le code sans chèque Null… Vraiment? .


Il y a un enregistrement nul dans le code ci-dessus - if(homeServices != null) {qui peut être changé en homeServices.ifPresent(h -> //action);
KrishPrabakar

23

J'aime les articles de Nat Pryce. Voici les liens:

Dans les articles, il existe également un lien vers un référentiel Git pour un type Java peut-être que je trouve intéressant, mais je ne pense pas que cela puisse à lui seul diminuer le gonflement du code de vérification. Après avoir fait quelques recherches sur Internet, je pense que ! = Le gonflement du code nul pourrait être diminué principalement par une conception soignée.


Michael Feathers a écrit un texte court et intéressant sur des approches comme celle que vous avez mentionnée: manuelp.newsblur.com/site/424
ivan.aguirre

21

J'ai essayé le NullObjectPatternmais pour moi ce n'est pas toujours la meilleure voie à suivre. Il y a parfois un "pas d'action" qui n'est pas approprié.

NullPointerExceptionest une exception Runtime qui signifie que c'est la faute des développeurs et avec suffisamment d'expérience, il vous indique exactement où est l'erreur.

Passons maintenant à la réponse:

Essayez de rendre tous vos attributs et ses accesseurs aussi privés que possible ou évitez de les exposer aux clients. Vous pouvez bien sûr avoir les valeurs d'argument dans le constructeur, mais en réduisant la portée, vous ne laissez pas la classe client passer une valeur non valide. Si vous devez modifier les valeurs, vous pouvez toujours en créer une nouvelle object. Vous ne vérifiez les valeurs dans le constructeur qu'une seule fois et dans les autres méthodes, vous pouvez être presque sûr que les valeurs ne sont pas nulles.

Bien sûr, l'expérience est la meilleure façon de comprendre et d'appliquer cette suggestion.

Octet!


19

La meilleure alternative pour Java 8 ou plus récent est probablement d'utiliser la Optionalclasse.

Optional stringToUse = Optional.of("optional is there");
stringToUse.ifPresent(System.out::println);

Ceci est particulièrement pratique pour les longues chaînes de valeurs nulles possibles. Exemple:

Optional<Integer> i = Optional.ofNullable(wsObject.getFoo())
    .map(f -> f.getBar())
    .map(b -> b.getBaz())
    .map(b -> b.getInt());

Exemple sur la façon de lever une exception sur null:

Optional optionalCarNull = Optional.ofNullable(someNull);
optionalCarNull.orElseThrow(IllegalStateException::new);

Java 7 a introduit la Objects.requireNonNullméthode qui peut être utile lorsque quelque chose doit être vérifié pour la non-nullité. Exemple:

String lowerVal = Objects.requireNonNull(someVar, "input cannot be null or empty").toLowerCase();

17

Puis-je y répondre de manière plus générale!

Nous rencontrons généralement ce problème lorsque les méthodes obtiennent les paramètres comme nous ne nous y attendions pas (un mauvais appel de méthode est la faute du programmeur). Par exemple: vous vous attendez à obtenir un objet, au lieu de cela, vous obtenez un null. Vous vous attendez à obtenir une chaîne avec au moins un caractère, au lieu de cela, vous obtenez une chaîne vide ...

Il n'y a donc pas de différence entre:

if(object == null){
   //you called my method badly!

}

ou

if(str.length() == 0){
   //you called my method badly again!
}

Ils veulent tous les deux s'assurer que nous avons reçu des paramètres valides, avant de faire d'autres fonctions.

Comme mentionné dans certaines autres réponses, pour éviter les problèmes ci-dessus, vous pouvez suivre le modèle de conception par contrat . Veuillez consulter http://en.wikipedia.org/wiki/Design_by_contract .

Pour implémenter ce modèle en java, vous pouvez utiliser des annotations java de base comme javax.annotation.NotNull ou utiliser des bibliothèques plus sophistiquées comme Hibernate Validator .

Juste un échantillon:

getCustomerAccounts(@NotEmpty String customerId,@Size(min = 1) String accountType)

Maintenant, vous pouvez développer en toute sécurité la fonction de base de votre méthode sans avoir à vérifier les paramètres d'entrée, ils protègent vos méthodes des paramètres inattendus.

Vous pouvez aller plus loin et vous assurer que seuls les pojos valides peuvent être créés dans votre application. (échantillon du site de validation d'hibernation)

public class Car {

   @NotNull
   private String manufacturer;

   @NotNull
   @Size(min = 2, max = 14)
   private String licensePlate;

   @Min(2)
   private int seatCount;

   // ...
}

javaxest, par définition, pas « noyau Java ».
Tomas

17

J'ignore fortement les réponses qui suggèrent d'utiliser les objets nuls dans toutes les situations. Ce modèle peut rompre le contrat et enfouir les problèmes de plus en plus profonds au lieu de les résoudre, sans mentionner que l'utilisation inappropriée créera une autre pile de code passe-partout qui nécessitera une maintenance future.

En réalité, si quelque chose renvoyé par une méthode peut être nul et que le code appelant doit prendre une décision à ce sujet, il devrait y avoir un appel antérieur qui garantit l'état.

Gardez également à l'esprit que ce modèle d'objet nul sera gourmand en mémoire s'il est utilisé sans précaution. Pour cela - l'instance d'un NullObject doit être partagée entre les propriétaires et ne pas être une instance unigue pour chacun d'eux.

De plus, je ne recommanderais pas d'utiliser ce modèle où le type est censé être une représentation de type primitif - comme des entités mathématiques, qui ne sont pas des scalaires: vecteurs, matrices, nombres complexes et objets POD (Plain Old Data), qui sont destinés à contenir l'état sous forme de types intégrés Java. Dans ce dernier cas, vous finirez par appeler des méthodes getter avec des résultats arbitraires. Par exemple, que doit renvoyer une méthode NullPerson.getName ()?

Cela vaut la peine d'envisager de tels cas afin d'éviter des résultats absurdes.


La solution avec "hasBackground ()" a un inconvénient - elle n'est pas thread-safe. Si vous devez appeler deux méthodes au lieu d'une, vous devez synchroniser la séquence entière dans un environnement multithread.
pkalinow

@pkalinow Vous avez fait un exemple artificiel pour souligner que cette solution a un inconvénient. Si le code n'est pas destiné à être exécuté dans une application multithread, il n'y a aucun inconvénient. Je pourrais vous mettre probablement 90% de votre code qui n'est pas thread-safe. Nous ne parlons pas ici de cet aspect du code, nous parlons de modèle de conception. Et le multithreading est un sujet à part entière.
luke1985

Bien sûr, dans une application à un seul thread, ce n'est pas un problème. J'ai fait ce commentaire parce que parfois c'est un problème.
pkalinow

@pkalinow Si vous étudiez ce sujet de plus près, vous découvrirez que le modèle de conception Null Object ne résoudra pas les problèmes de multithreading. Ce n'est donc pas pertinent. Et pour être honnête, j'ai trouvé des endroits où ce modèle s'intégrerait bien, donc ma réponse originale est un peu fausse, en fait.
luke1985

16
  1. Ne jamais initialiser les variables à null.
  2. Si (1) n'est pas possible, initialisez toutes les collections et tableaux pour vider les collections / tableaux.

Faire cela dans votre propre code et vous pouvez éviter les contrôles! = Null.

La plupart du temps, les contrôles nuls semblent protéger les boucles sur les collections ou les tableaux, alors initialisez-les simplement vides, vous n'aurez pas besoin de contrôles nuls.

// Bad
ArrayList<String> lemmings;
String[] names;

void checkLemmings() {
    if (lemmings != null) for(lemming: lemmings) {
        // do something
    }
}



// Good
ArrayList<String> lemmings = new ArrayList<String>();
String[] names = {};

void checkLemmings() {
    for(lemming: lemmings) {
        // do something
    }
}

Il y a une petite surcharge, mais cela en vaut la peine pour un code plus propre et moins d'exceptions NullPointerExceptions.



3
+1 Je suis d'accord avec cela. Vous ne devez jamais retourner des objets à moitié initialisés. Le code lié à Jaxb et le code du bean ne sont pas connus pour cela. C'est une mauvaise pratique. Toutes les collections doivent être initialisées et tous les objets doivent exister sans (idéalement) aucune référence nulle. Considérez un objet qui contient une collection. Vérifier que l'objet n'est pas nul, que la collection n'est pas nulle et que la collection ne contient pas d'objets null est déraisonnable et insensé.
ggb667

15

Il s'agit de l'erreur la plus courante survenue pour la plupart des développeurs.

Nous avons plusieurs façons de gérer cela.

Approche 1:

org.apache.commons.lang.Validate //using apache framework

notNull (objet Object, message String)

Approche 2:

if(someObject!=null){ // simply checking against null
}

Approche 3:

@isNull @Nullable  // using annotation based validation

Approche 4:

// by writing static method and calling it across whereever we needed to check the validation

static <T> T isNull(someObject e){  
   if(e == null){
      throw new NullPointerException();
   }
   return e;
}

Un d. 4. Ce n'est pas très utile - lorsque vous vérifiez si un pointeur est nul, vous voulez probablement appeler une méthode dessus. L'appel d'une méthode sur null vous donne le même comportement - NullPointerException.
pkalinow

11
public static <T> T ifNull(T toCheck, T ifNull) {
    if (toCheck == null) {
           return ifNull;
    }
    return toCheck;
}

2
Quel est le problème avec cette méthode, je pense que @tltester veut juste donner une valeur par défaut si c'est null, ce qui est logique.
Sawyer

1
Il y a une telle méthode dans commons-lang Apache: ObjectUtils.defaultIfNull(). Il y en a un de plus général ObjectUtils.firstNonNull()firstNonNull(bestChoice, secondBest, thirdBest, fallBack);
:,
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.