web-dev-qa-db-fra.com

Java: comment vérifier efficacement les pointeurs nuls

Il existe quelques modèles pour vérifier si un paramètre d'une méthode a reçu une valeur null.

Tout d'abord, le classique. C'est courant dans le code créé par soi-même et évident à comprendre.

public void method1(String arg) {
  if (arg == null) {
    throw new NullPointerException("arg");
  }
}

Deuxièmement, vous pouvez utiliser un cadre existant. Ce code est un peu plus joli car il n'occupe qu'une seule ligne. L'inconvénient est qu'il appelle potentiellement une autre méthode, qui pourrait ralentir un peu le code, en fonction du compilateur.

public void method2(String arg) {
  Assert.notNull(arg, "arg");
}

Troisièmement, vous pouvez essayer d'appeler une méthode sans effets secondaires sur l'objet. Cela peut sembler étrange au début, mais il a moins de jetons que les versions ci-dessus.

public void method3(String arg) {
  arg.getClass();
}

Je n'ai pas vu le troisième modèle largement utilisé, et j'ai presque l'impression de l'avoir inventé moi-même. Je l'aime pour sa brièveté et parce que le compilateur a de bonnes chances de l'optimiser complètement ou de le convertir en une seule instruction machine. Je compile également mon code avec des informations de numéro de ligne, donc si un NullPointerException est lancé, je peux le retracer jusqu'à la variable exacte, car je n'ai qu'un seul contrôle par ligne.

Quel chèque préférez-vous et pourquoi?

25
Roland Illig

Approche # 3:arg.getClass(); est intelligent, mais à moins que cet idiome ne soit largement adopté, je Je préfère les méthodes les plus claires et les plus verbeuses plutôt que de sauvegarder quelques caractères. Je suis un programmeur "écrire une fois, lire plusieurs".

Les autres approches sont auto-documentées: il y a un message de journal que vous pouvez utiliser pour clarifier ce qui s'est passé - ce message de journal est utilisé lors de la lecture du code et également au moment de l'exécution. arg.getClass(), tel quel, n'est pas auto-documenté. Vous pouvez utiliser un commentaire au moins pour clarifier aux réviseurs du code:

arg.getClass(); // null check

Mais vous n'avez toujours pas la possibilité de mettre un message spécifique dans le runtime comme vous le pouvez avec les autres méthodes.


Approach # 1 vs # 2 (null-check + NPE/IAE vs assert): J'essaye de suivre les directives comme ça:

http://data.opengeo.org/GEOT-290810-1755-708.pdf

  • Utilisez assert pour vérifier les paramètres des méthodes privées
    assert param > 0;

  • Utilisez null check + IllegalArgumentException pour vérifier les paramètres des méthodes publiques
    if (param == null) throw new IllegalArgumentException("param cannot be null");

  • Utilisez une vérification nulle + NullPointerException si nécessaire
    if (getChild() == null) throw new NullPointerException("node must have children");


[~ # ~] cependant [~ # ~], puisque cette question peut être de capter le potentiel null est le plus efficace, alors je dois mentionner que ma méthode préférée pour traiter null utilise l'analyse statique, par exemple annotations de type (par exemple @NonNull) a la JSR-305. Mon outil préféré pour les vérifier est:

Le framework Checker:
Types enfichables personnalisés pour Java
https://checkerframework.org/manual/#checker-guarantees

Si c'est mon projet (par exemple pas une bibliothèque avec une API publique) et si je peux utiliser Checker Framework partout:

  • Je peux documenter mon intention plus clairement dans l'API (par exemple, ce paramètre peut ne pas être nul (par défaut), mais celui-ci peut être nul (@Nullable; La méthode peut renvoyer null; etc.). Cette annotation est correcte à la déclaration, plutôt que plus loin dans le Javadoc, il est donc beaucoup plus susceptible d'être maintenu.

  • l'analyse statique est plus efficace que toute vérification à l'exécution

  • l'analyse statique signalera à l'avance les failles logiques potentielles (par exemple, que j'ai essayé de transmettre une variable qui peut être nulle à une méthode qui n'accepte qu'un paramètre non nul) plutôt que de dépendre du problème survenant à l'exécution.

Un autre bonus est que l'outil me permet de mettre les annotations dans un commentaire (par exemple `/ @ Nullable /), donc mon code de bibliothèque peut être compatible avec les projets annotés de type et non annotés de type projets (pas que j'aie aucun de ceux-ci).


Au cas où le lien serait à nouveau mort , voici la section du Guide du développeur GeoTools:

http://data.opengeo.org/GEOT-290810-1755-708.pdf

5.1.7 Utilisation d'assertions, IllegalArgumentException et NPE

Le langage Java a depuis quelques années maintenant rendu un mot-clé assert disponible; ce mot-clé peut être utilisé pour effectuer uniquement des vérifications de débogage. Bien qu'il y ait plusieurs utilisations de cette fonction, la plus courante consiste à vérifier les paramètres de méthode sur les méthodes privées (non publiques). D'autres utilisations sont les post-conditions et les invariants.

Référence: Programmation avec assertions

Les pré-conditions (comme les vérifications d'arguments dans les méthodes privées) sont généralement des cibles faciles pour les assertions. Les post-conditions et les invariants sont parfois moins simples mais plus précieux, car les conditions non triviales ont plus de risques d'être brisées.

  • Exemple 1: Après une projection cartographique dans le module de référencement, une assertion effectue la projection cartographique inverse et vérifie le résultat avec le point d'origine (post-condition) .
  • Exemple 2: Dans les implémentations DirectPosition.equals (Object), si le résultat est vrai, alors l'assertion garantit que hashCode () est identique comme requis par le Contrat d'objet.

Utilisez Assert pour vérifier les paramètres des méthodes privées

private double scale( int scaleDenominator ){
 assert scaleDenominator > 0;
 return 1 / (double) scaleDenominator;
}

Vous pouvez activer les assertions avec le paramètre de ligne de commande suivant:

Java -ea MyApp

Vous ne pouvez activer que les assertions GeoTools avec le paramètre de ligne de commande suivant:

Java -ea:org.geotools MyApp

Vous pouvez désactiver les assertions pour un package spécifique comme indiqué ici:

Java -ea:org.geotools -da:org.geotools.referencing MyApp

Utilisez IllegalArgumentExceptions pour vérifier les paramètres des méthodes publiques

L'utilisation d'assertions sur les méthodes publiques est strictement déconseillée; parce que l'erreur rapportée a été commise dans le code client - soyez honnête et dites-leur dès le départ avec une exception IllegalArgumentException quand ils ont raté.

public double toScale( int scaleDenominator ){
 if( scaleDenominator > 0 ){
 throw new IllegalArgumentException( "scaleDenominator must be greater than 0");
 }
 return 1 / (double) scaleDenominator;
}

Utilisez NullPointerException si nécessaire

Si possible, effectuez vos propres vérifications nulles; lancer une IllegalArgumentException ou NullPointerException avec des informations détaillées sur ce qui ne va pas.

public double toScale( Integer scaleDenominator ){
 if( scaleDenominator == null ){
 throw new NullPointerException( "scaleDenominator must be provided");
 }
 if( scaleDenominator > 0 ){
 throw new IllegalArgumentException( "scaleDenominator must be greater than 0");
 }
 return 1 / (double) scaleDenominator;
}
23
Bert F

N'optimisez-vous pas un biiiiiiiiiiiiiiit trop prématurément !?

J'utiliserais simplement le premier. C'est clair et concis.

Je travaille rarement avec Java, mais je suppose qu'il existe un moyen de faire fonctionner Assert uniquement sur les versions de débogage, ce serait donc un non-non.

Le troisième me donne la chair de poule, et je pense que je recourrais immédiatement à la violence si jamais je la voyais dans le code. On ne sait pas du tout ce qu'il fait.

12
Willem van Rumpt

Vous pouvez utiliser la classe utilitaire d'objets.

public void method1(String arg) {
  Objects.requireNonNull(arg);
}

voir http://docs.Oracle.com/javase/7/docs/api/Java/util/Objects.html#requireNonNull%28T%29

6
räph

Vous ne devriez pas lancer NullPointerException. Si vous voulez une NullPointerException, ne vérifiez simplement pas la valeur et elle sera lancée automatiquement lorsque le paramètre est nul et que vous essayez de le déréférencer.

Consultez les classes Apache commons lang Validate et StringUtils.
Validate.notNull(variable) il lancera une IllegalArgumentException si "variable" est nul.
Validate.notEmpty(variable) lancera une IllegalArgumentException si "variable" est vide (longueur nulle ou nulle ".
Peut-être encore mieux:
String trimmedValue = StringUtils.trimToEmpty(variable) garantira que "trimmedValue" n'est jamais nul. Si "variable" est nul, "trimmedValue" sera la chaîne vide ("").

5
DwB

La première méthode est ma préférence car elle transmet le plus d'intention. Il y a souvent des raccourcis qui peuvent être pris dans la programmation, mais mon avis est qu'un code plus court n'est pas toujours un meilleur code.

3
Bnjmn

À mon avis, la troisième méthode pose trois problèmes:

  1. L'intention n'est pas claire pour le lecteur occasionnel.
  2. Même si vous disposez d'informations sur le numéro de ligne, les numéros de ligne changent. Dans un vrai système de production, savoir qu'il y avait un problème dans SomeClass à la ligne 100 ne vous donne pas toutes les informations dont vous avez besoin. Vous devez également connaître la révision du fichier en question et pouvoir accéder à cette révision. Dans l'ensemble, beaucoup de tracas pour ce qui semble être très peu avantageux.
  3. On ne sait pas du tout pourquoi vous pensez que l'appel à arg.getClass peut être optimisé. C'est une méthode native. À moins que HotSpot ne soit codé pour avoir une connaissance spécifique de la méthode pour cette éventualité exacte, il laissera probablement l'appel seul car il ne peut pas connaître les effets secondaires potentiels du code C qui est appelé.

Ma préférence est d'utiliser # 1 chaque fois que je pense qu'il y a un besoin d'une vérification nulle. Avoir le nom de la variable dans le message d'erreur est idéal pour déterminer rapidement ce qui ne va pas exactement.

P.S. Je ne pense pas que l'optimisation du nombre de jetons dans le fichier source soit un critère très utile.

3
NPE

x == null est super rapide, et il peut s'agir de quelques horloges CPU (y compris la prédiction de branche qui va réussir). AssertNotNull sera intégré, donc aucune différence.

x.getClass () ne doit pas être plus rapide que x == null même s'il utilise trap. (raison: le x sera dans un registre et la vérification d'un registre par rapport à une valeur immédiate est rapide, la branche sera également prédite correctement)

En bout de ligne: à moins que vous ne fassiez quelque chose de vraiment bizarre, il serait optimisé par la JVM.

2
bestsss

Bien que je sois d'accord avec le consensus général de préférer éviter le hack getClass (), il convient de noter qu'à partir de la version 1.8.0_121 d'OpenJDK, javac utilisera le hack getClass () pour insérer des vérifications nulles avant de créer des expressions lambda. Par exemple, considérez:

public class NullCheck {
  public static void main(String[] args) {
    Object o = null;
    Runnable r = o::hashCode;
  }
}

Après avoir compilé ceci avec javac, vous pouvez utiliser javap pour voir le bytecode en exécutant javap -c NullCheck. La sortie est (en partie):

Compiled from "NullCheck.Java"
public class NullCheck {
  public NullCheck();
    Code:
       0: aload_0
       1: invokespecial #1    // Method Java/lang/Object."<init>":()V
       4: return

  public static void main(Java.lang.String[]);
    Code:
       0: aconst_null
       1: astore_1
       2: aload_1
       3: dup
       4: invokevirtual #2    // Method Java/lang/Object.getClass:()Ljava/lang/Class;
       7: pop
       8: invokedynamic #3, 0 // InvokeDynamic #0:run:(Ljava/lang/Object;)Ljava/lang/Runnable;
      13: astore_2
      14: return
}

Les instructions définies aux "lignes" 3, 4 et 7 invoquent fondamentalement o.getClass () et rejettent le résultat. Si vous exécutez NullCheck, vous obtiendrez une NullPointerException lancée à partir de la ligne 4.

Si c'est quelque chose que les Java ont conclu comme une optimisation nécessaire, ou c'est juste un hack bon marché, je ne sais pas. Cependant, basé sur le commentaire de John Rose à https : //bugs.openjdk.Java.net/browse/JDK-8042127? focusCommentId = 13612451 & page = com.atlassian.jira.plugin.system.issuetabpanels: comment-tabpanel # comment-13612451 , je soupçonne que cela peut effectivement être le cas où le hack getClass (), qui produit une vérification nulle implicite, peut être un peu plus performant que son homologue explicite. Cela dit, j'éviterais de l'utiliser à moins que prudent l'analyse comparative a montré qu'elle faisait une différence appréciable.

(Il est intéressant de noter que le compilateur Eclipse For Java (ECJ) n'inclut pas cette vérification nulle, et exécute NullCheck comme compilé) par la CJUE ne lancera pas de NPE.)

1
Ian Robertson

La première option est la plus simple et la plus claire.

Ce n'est pas courant en Java, mais en C et C++ où l'opérateur = peut être inclus dans une expression de l'instruction if et donc conduire à des erreurs, il est souvent recommandé de basculer entre la variable et la constante comme ceci:

if (NULL == variable) {
   ...
}

au lieu de:

if (variable == NULL) {
   ...
}

éviter les erreurs de type:

if (variable = NULL) { // Assignment!
   ...
}

Si vous apportez la modification, le compilateur trouvera ce genre d'erreurs pour vous.

1
Marcelo

J'utiliserais le mécanisme intégré Java assert.

assert arg != null;

L'avantage de ceci par rapport à toutes les autres méthodes est qu'il peut être désactivé.

0
biziclop

Il n'y a pas de vote pour celui-ci, mais j'utilise une légère variation de # 2, comme

erStr += nullCheck (varName, String errMsg); // returns formatted error message

Raison d'être: (1) Je peux boucler sur un tas d'arguments, (2) La méthode nullCheck est cachée dans une superclasse et (3) à la fin de la boucle,

if (erStr.length() > 0)
    // Send out complete error message to client
else
    // do stuff with variables

Dans la méthode superclass, votre # 3 a l'air sympa, mais je ne lancerais pas d'exception (à quoi ça sert, quelqu'un doit le gérer, et en tant que conteneur de servlet, Tomcat l'ignorera, donc ça pourrait aussi bien être ceci ( )) Cordialement, - MS.

0
Manidip Sengupta

Je préfère la méthode 4, 5 ou 6, avec le n ° 4 appliqué aux méthodes API publiques et le 5/6 pour les méthodes internes, bien que le n ° 6 soit plus fréquemment appliqué aux méthodes publiques.

/**
 * Method 4.
 * @param arg A String that should have some method called upon it. Will be ignored if
 * null, empty or whitespace only.
 */
public void method4(String arg) {
   // commons stringutils
   if (StringUtils.isNotBlank(arg) {
       arg.trim();
   }
}

/**
 * Method 5.
 * @param arg A String that should have some method called upon it. Shouldn't be null.
 */
public void method5(String arg) {
   // Let NPE sort 'em out.
   arg.trim();
}

/**
 * Method 6.
 * @param arg A String that should have some method called upon it. Shouldn't be null.
 */
public void method5(String arg) {
   // use asserts, expect asserts to be enabled during dev-time, so that developers
   // that refuse to read the documentations get slapped on the wrist for still passing
   // null. Assert is a no-op if the -ae param is not passed to the jvm, so 0 overhead.

   assert arg != null : "Arg cannot be null"; // insert insult here.
   arg.trim();
}

La meilleure solution pour gérer les valeurs nulles consiste à ne pas utiliser de valeurs nulles. Enveloppez les méthodes tierces ou de bibliothèque qui peuvent renvoyer des valeurs nulles avec des protections nulles, en remplaçant la valeur par quelque chose qui a du sens (comme une chaîne vide) mais ne fait rien lorsqu'il est utilisé. Lancez des NPE si un null ne doit vraiment pas être passé, en particulier dans les méthodes de définition où l'objet passé n'est pas appelé tout de suite.

0
fwielstra

Première méthode. Je ne ferais jamais la deuxième ou la troisième méthode, sauf si elles sont mises en œuvre efficacement par la JVM sous-jacente. Sinon, ces deux exemples ne sont que des exemples optimaux prématurés (le troisième ayant une pénalité de performance possible - vous ne voulez pas traiter et accéder aux métadonnées de classe dans les points d'accès généraux.)

Le problème avec les NPE est que ce sont des choses qui recoupent de nombreux aspects de la programmation (et mes aspects, je veux dire quelque chose de plus profond et de plus profond que l'AOP). C'est un problème de conception de langage (ne pas dire que le langage est mauvais, mais qu'il s'agit d'une lacune fondamentale ... de tout langage qui autorise des pointeurs ou des références nuls.)

En tant que tel, il est préférable de le traiter explicitement comme dans la première méthode. Toutes les autres méthodes sont des tentatives (échouées) de simplifier un modèle d'opérations, une complexité inévitable qui existe sur le modèle de programmation sous-jacent.

C'est une balle que nous ne pouvons éviter de mordre. Traitez-le explicitement tel qu'il est - dans le cas général qui est - le moins douloureux sur la route.

0
luis.espinal