Comment utiliser plus efficacement les annotations @Nullable et @Nonnull?

140

Je peux voir que @Nullableet les @Nonnullannotations pourraient être utiles pour empêcher les NullPointerExceptions, mais elles ne se propagent pas très loin.

  • L'efficacité de ces annotations diminue complètement après un niveau d'indirection, donc si vous n'en ajoutez que quelques-unes, elles ne se propagent pas très loin.
  • Étant donné que ces annotations ne sont pas bien appliquées, il y a un risque de supposer qu'une valeur marquée par @Nonnulln'est pas nulle et par conséquent de ne pas effectuer de vérifications nulles.

Le code ci - dessous provoque un paramètre marqué par @Nonnullêtre nullsans soulever de plaintes. Il jette un NullPointerExceptionquand il est exécuté.

public class Clazz {
    public static void main(String[] args){
        Clazz clazz = new Clazz();

        // this line raises a complaint with the IDE (IntelliJ 11)
        clazz.directPathToA(null);

        // this line does not
        clazz.indirectPathToA(null); 
    }

    public void indirectPathToA(Integer y){
        directPathToA(y);
    }

    public void directPathToA(@Nonnull Integer x){
        x.toString(); // do stuff to x        
    }
}

Existe-t-il un moyen de rendre ces annotations plus strictement appliquées et / ou de se propager davantage?

Mike Rylander
la source
1
J'aime l'idée de @Nullableou @Nonnull, mais s'ils en valent la peine, il est très "susceptible de
susciter un
Je pense que la façon de passer à un monde où cela provoque une erreur ou un avertissement du compilateur est d'exiger un cast en @Nonnulllors de l'appel d'une @Nonnullméthode avec une variable Nullable. Bien sûr, la conversion avec une annotation n'est pas possible dans Java 7, mais Java 8 ajoutera la possibilité d'appliquer des annotations à l'utilisation d'une variable, y compris les casts. Cela peut donc devenir possible à implémenter en Java 8.
Theodore Murdock
1
@TheodoreMurdock, oui, dans Java 8, un cast (@NonNull Integer) yest syntaxiquement possible, mais un compilateur n'est pas autorisé à émettre un code d'octet spécifique basé sur l'annotation. Pour les assertions d'exécution, de minuscules méthodes d'assistance sont suffisantes, comme indiqué dans bugs.eclipse.org/442103 (par exemple directPathToA(assertNonNull(y))) - mais attention, cela ne fait qu'échouer rapidement. Le seul moyen sûr est d'effectuer une vérification réelle de null (plus, espérons-le, une implémentation alternative dans la branche else).
Stephan Herrmann
1
Il serait utile dans cette question de dire de qui @Nonnullet dont @Nullablevous parlez, car il y a plusieurs annoations similaires (voir cette question ). Parlez-vous des annotations dans le package javax.annotation?
James Dunn
1
@TJamesBoone Pour le contexte de cette question, peu importe, il s'agissait de savoir comment les utiliser efficacement.
Mike Rylander

Réponses:

66

Réponse courte: je suppose que ces annotations ne sont utiles que pour que votre IDE vous avertisse des erreurs de pointeur potentiellement nulles.

Comme dit dans le livre "Clean Code", vous devriez vérifier les paramètres de votre méthode publique et aussi éviter de vérifier les invariants.

Un autre bon conseil est de ne jamais renvoyer de valeurs nulles, mais d'utiliser à la place un motif d'objet nul .

Pedro Boechat
la source
10
Pour les valeurs de retour pouvant être vides, je suggère fortement d'utiliser le Optionaltype au lieu de plainnull
Patrick
7
Optionnel n'est pas meilleur que "null". Facultatif # get () lève NoSuchElementException tandis qu'une utilisation de la valeur null lève NullPointerException. Les deux sont des exceptions RuntimeException sans description significative. Je préfère les variables Nullable.
30
4
@ 30thh pourquoi utiliseriez-vous Optional.get () directement et non Optional.isPresent () ou Optional.map en premier?
GauravJ
7
@GauravJ Pourquoi utiliseriez-vous une variable Nullable directement et ne pas vérifier si elle est NULL en premier? ;-)
30
5
La différence entre Optionalet nullable dans ce cas est que cela indique Optionalmieux que cette valeur peut être intentionnellement vide. Certes, ce n'est pas une baguette magique et pendant l'exécution, elle peut échouer exactement de la même manière qu'une variable Nullable. Cependant, la réception de l'API par le programmeur est meilleure Optionalà mon avis.
user1053510
31

Outre que votre IDE vous donne des conseils lorsque vous passez nullà des méthodes qui s'attendent à ce que l'argument ne soit pas nul, il y a d'autres avantages:

  • Les outils d'analyse de code statique peuvent tester le même que votre IDE (par exemple FindBugs)
  • Vous pouvez utiliser AOP pour vérifier ces assertions

Cela peut aider votre code à être plus maintenable (puisque vous n'avez pas besoin de nullvérifications) et moins sujet aux erreurs.

Uwe Plonus
la source
9
Je sympathise avec l'OP ici, car même si vous citez ces deux avantages, dans les deux cas, vous avez utilisé le mot «peut». Cela signifie qu'il n'y a aucune garantie que ces contrôles se produiront réellement. Désormais, cette différence de comportement pourrait être utile pour les tests sensibles aux performances que vous souhaitez éviter d'exécuter en mode production, pour lesquels nous avons assert. Je trouve @Nullableet @Nonnullêtre des idées utiles, mais j'aimerais plus de force derrière elles, plutôt que de faire des hypothèses sur ce que l'on pourrait en faire, ce qui laisse encore ouverte la possibilité de ne rien faire avec elles.
seh
2
La question est par où commencer. Pour le moment, ses anntations sont facultatives. Parfois, j'aimerais que ce ne soit pas le cas, car dans certaines circonstances, il serait utile de les appliquer ...
Uwe Plonus
Puis-je vous demander à quoi vous faites référence ici?
Chris.Zou
@ Chris.Zou AOP signifie programmation orientée aspect, par exemple AspectJ
Uwe Plonus
13

Je pense que cette question originale pointe indirectement vers une recommandation générale selon laquelle la vérification du pointeur nul au moment de l'exécution est toujours nécessaire, même si @NonNull est utilisé. Reportez-vous au lien suivant:

Nouvelles annotations de type de Java 8

Dans le blog ci-dessus, il est recommandé que:

Les annotations de type facultatives ne remplacent pas la validation d'exécution Avant les annotations de type, l'emplacement principal pour décrire des éléments tels que la nullité ou les plages était dans le javadoc. Avec les annotations de type, cette communication entre dans le bytecode d'une manière pour la vérification à la compilation. Votre code doit toujours effectuer la validation d'exécution.

Jonathanzh
la source
1
Compris, mais les vérifications de lint par défaut avertissent que les vérifications nulles à l'exécution sont inutiles, ce qui, à première vue, semble décourager cette recommandation.
swooby
1
@swooby Habituellement, j'ignore les avertissements de charpie si je suis sûr que mon code est correct. Ces avertissements ne sont pas des erreurs.
jonathanzh
12

En compilant l'exemple d'origine dans Eclipse à la conformité 1.8 et avec l'analyse nulle basée sur les annotations activée, nous obtenons cet avertissement:

    directPathToA(y);
                  ^
Null type safety (type annotations): The expression of type 'Integer' needs unchecked conversion to conform to '@NonNull Integer'

Cet avertissement est formulé par analogie avec les avertissements que vous recevez lorsque vous mélangez du code généré avec du code hérité en utilisant des types bruts ("conversion non cochée"). Nous avons exactement la même situation ici: la méthode indirectPathToA()a une signature «héritée» en ce qu'elle ne spécifie aucun contrat nul. Les outils peuvent facilement signaler cela, ils vous poursuivront donc dans toutes les allées où des annotations nulles doivent être propagées mais ne le sont pas encore.

Et lorsque vous utilisez un intelligent, @NonNullByDefaultnous n'avons même pas à le dire à chaque fois.

En d'autres termes: le fait que les annotations nulles "se propagent très loin" peut dépendre de l'outil que vous utilisez et de la rigueur avec laquelle vous vous occupez de tous les avertissements émis par l'outil. Avec les annotations nulles TYPE_USE, vous avez enfin la possibilité de laisser l'outil vous avertir de tous les NPE possibles dans votre programme, car la nullité est devenue une propriété intrinsèque du système de types.

Stephan Herrmann
la source
8

Je conviens que les annotations "ne se propagent pas très loin". Cependant, je vois l'erreur du côté du programmeur.

Je comprends l' Nonnullannotation comme une documentation. La méthode suivante exprime qu'elle requiert (comme condition préalable) un argument non nul x.

    public void directPathToA(@Nonnull Integer x){
        x.toString(); // do stuff to x        
    }

L'extrait de code suivant contient alors un bogue. La méthode appelle directPathToA()sans appliquer ce qui yest non nul (c'est-à-dire qu'elle ne garantit pas la condition préalable de la méthode appelée). Une possibilité est d'ajouter également une Nonnullannotation à indirectPathToA()(propager la précondition). La deuxième possibilité consiste à vérifier la nullité de yin indirectPathToA()et à éviter l'appel à directPathToA()when yis null.

    public void indirectPathToA(Integer y){
        directPathToA(y);
    }
Andrés
la source
1
Propager le @Nonnull to have indirectPathToA(@Nonnull Integer y)est à mon humble avis une mauvaise pratique: vous devrez maintenir la propagation sur la pile d'appels complète (donc si vous ajoutez un nullenregistrement directPathToA(), vous devrez remplacer @Nonnullpar @Nullabledans la pile d'appels complète). Ce serait un énorme effort de maintenance pour les grandes applications.
Julien Kronegg
L'annotation @Nonnull souligne simplement que la vérification nulle de l'argument est de votre côté (vous devez garantir que vous passez une valeur non nulle). Ce n'est pas la responsabilité de la méthode.
Alexander Drobyshevsky
@Nonnull est également une chose raisonnable lorsque la valeur nulle n'a aucun sens pour cette méthode
Alexander Drobyshevsky
5

Ce que je fais dans mes projets est d'activer l'option suivante dans l'inspection de code "Conditions constantes et exceptions":
Suggérer une annotation @Nullable pour les méthodes qui peuvent éventuellement renvoyer null et signaler des valeurs nullables passées à des paramètres non annotés Inspections

Lorsqu'il est activé, tous les paramètres non annotés seront traités comme non nuls et vous verrez donc également un avertissement sur votre appel indirect:

clazz.indirectPathToA(null); 

Pour des vérifications encore plus solides, le Checker Framework peut être un bon choix (voir ce joli tutoriel .
Remarque : je ne l'ai pas encore utilisé et il peut y avoir des problèmes avec le compilateur Jack: voir ce rapport de bogue

TmTron
la source
4

En Java, j'utiliserais le type facultatif de Guava . Étant un type réel, vous obtenez des garanties du compilateur sur son utilisation. Il est facile de le contourner et d'obtenir un NullPointerException, mais au moins la signature de la méthode communique clairement ce qu'elle attend en tant qu'argument ou ce qu'elle pourrait renvoyer.

Ionuț G. Stan
la source
16
Vous devez être prudent avec cela. Optionnel ne doit être utilisé que lorsqu'une valeur est réellement optionnelle et dont l'absence est utilisée comme porte de décision pour une logique ultérieure. J'ai vu cela abusé avec le remplacement général des objets par des options et des vérifications nulles remplacées par des vérifications de présence qui ne sont pas pertinentes.
Christopher Perry
si vous ciblez JDK 8 ou plus récent, préférez utiliser à la java.util.Optionalplace de la classe de Guava. Voir les notes / comparaison de Guava pour plus de détails sur les différences.
AndrewF
1
« chèques nuls remplacés par des contrôles de présence qui manque le point » pouvez - vous élaborer, puis, sur ce point est ? Ce n'est pas la seule raison pour les options, à mon avis, mais c'est certainement de loin la plus grande et la meilleure.
scubbo
4

Si vous utilisez Kotlin, il prend en charge ces annotations de nullabilité dans son compilateur et vous empêchera de passer un null à une méthode java qui nécessite un argument non nul. Même si cette question était à l'origine ciblée sur Java, je mentionne cette fonctionnalité Kotlin car elle est spécifiquement ciblée sur ces annotations Java et la question était "Y a-t-il un moyen de rendre ces annotations plus strictement appliquées et / ou de se propager davantage?" et cette fonctionnalité rend ces annotations plus strictement appliquées .

Classe Java utilisant l' @NotNullannotation

public class MyJavaClazz {
    public void foo(@NotNull String myString) {
        // will result in an NPE if myString is null
        myString.hashCode();
    }
}

Classe Kotlin appelant la classe Java et passant null pour l'argument annoté avec @NotNull

class MyKotlinClazz {
    fun foo() {
        MyJavaClazz().foo(null)
    }
}  

Erreur du compilateur Kotlin lors de l'application de l' @NotNullannotation.

Error:(5, 27) Kotlin: Null can not be a value of a non-null type String

voir: http://kotlinlang.org/docs/reference/java-interop.html#nullability-annotations

Mike Rylander
la source
3
La question concerne Java, par sa première balise, et non Kotlin.
seh
1
@seh voir la mise à jour pour savoir pourquoi cette réponse est pertinente pour cette question.
Mike Rylander
2
C'est suffisant. C'est une fonctionnalité intéressante de Kotlin. Je ne pense tout simplement pas que cela satisfera ceux qui viennent ici pour en savoir plus sur Java.
seh
mais l'accès myString.hashCode()sera toujours lancer NPE même s'il @NotNulln'est pas ajouté en paramètre. Alors, quoi de plus spécifique à propos de l'ajout?
kAmol
@kAmol La différence ici est que lorsque vous utilisez Kotlin, vous obtiendrez une erreur de compilation au lieu d'une erreur d' exécution . L'annotation vous informe que vous, le développeur, devez vous assurer qu'une valeur nulle n'est pas transmise. Cela n'empêchera pas la transmission d'une valeur nulle au moment de l'exécution, mais cela vous empêchera d'écrire du code qui appelle cette méthode avec une valeur nulle (ou avec une fonction qui peut retourner null).
Mike Rylander
-4

Depuis la nouvelle fonctionnalité Java 8 en option, vous ne devez plus utiliser @Nullable ou @Notnull dans votre propre code . Prenons l'exemple ci-dessous:

public void printValue(@Nullable myValue) {
    if (myValue != null) {
        System.out.print(myValue);
    } else {
        System.out.print("I dont have a value");
}

Il pourrait être réécrit avec:

public void printValue(Optional<String> myValue) {
    if (myValue.ifPresent) {
        System.out.print(myValue.get());
    } else {
        System.out.print("I dont have a value");
}

L'utilisation d'une option vous oblige à vérifier la valeur nulle . Dans le code ci-dessus, vous ne pouvez accéder à la valeur qu'en appelant la getméthode.

Un autre avantage est que le code devient plus lisible . Avec l'ajout de Java 9 ifPresentOrElse , la fonction pourrait même être écrite comme suit :

public void printValue(Optional<String> myValue) {
    myValue.ifPresentOrElse(
        v -> System.out.print(v),
        () -> System.out.print("I dont have a value"),
    )
}
Mathieu Gemard
la source
Même avec Optional, il existe encore de nombreuses bibliothèques et frameworks qui utilisent ces annotations de sorte qu'il n'est pas possible de mettre à jour / remplacer toutes vos dépendances par des versions mises à jour pour utiliser des options. Optionalpeut toutefois aider dans les situations où vous utilisez null dans votre propre code.
Mike Rylander du