Depuis JSR 305 (dont l'objectif était de normaliser @NonNull
et @Nullable
) est en sommeil depuis plusieurs années, je crains qu'il n'y ait pas de bonne réponse. Tout ce que nous pouvons faire est de trouver une solution pragmatique et la mienne est la suivante:
Syntaxe
D'un point de vue purement stylistique, je voudrais éviter toute référence à l'IDE, au framework ou à toute boîte à outils à l'exception de Java lui-même.
Cela exclut:
android.support.annotation
edu.umd.cs.findbugs.annotations
org.eclipse.jdt.annotation
org.jetbrains.annotations
org.checkerframework.checker.nullness.qual
lombok.NonNull
Ce qui nous laisse avec javax.validation.constraints
ou javax.annotation
. Le premier est livré avec JEE. Si c'est mieux que cela javax.annotation
, qui pourrait éventuellement arriver avec JSE ou jamais du tout, c'est un sujet de débat. Personnellement, je préfère javax.annotation
parce que je n'aimerais pas la dépendance JEE.
Cela nous laisse avec
javax.annotation
qui est aussi le plus court.
Il n'y a qu'une syntaxe qui serait même mieux: java.annotation.Nullable
. Comme d'autres packages sont passés de javax
à java
dans le passé, l'annotation javax serait un pas dans la bonne direction.
la mise en oeuvre
J'espérais qu'ils ont tous fondamentalement la même implémentation triviale, mais une analyse détaillée a montré que ce n'est pas vrai.
D'abord pour les similitudes:
Les @NonNull
annotations ont toutes la ligne
public @interface NonNull {}
à l'exception de
org.jetbrains.annotations
qui l'appelle @NotNull
et a une implémentation triviale
javax.annotation
qui a une mise en œuvre plus longue
javax.validation.constraints
qui l'appelle aussi @NotNull
et a une implémentation
Les @Nullable
annotations ont toutes la ligne
public @interface Nullable {}
sauf (encore) le org.jetbrains.annotations
avec leur implémentation triviale.
Pour les différences:
Un frappant est que
javax.annotation
javax.validation.constraints
org.checkerframework.checker.nullness.qual
tous ont des annotations d'exécution ( @Retention(RUNTIME)
), tandis que
android.support.annotation
edu.umd.cs.findbugs.annotations
org.eclipse.jdt.annotation
org.jetbrains.annotations
ne sont que du temps de compilation ( @Retention(CLASS)
).
Comme décrit dans cette réponse SO, l'impact des annotations d'exécution est plus petit qu'on ne le pense, mais elles ont l'avantage d'activer des outils pour effectuer des vérifications d'exécution en plus de celles de la compilation.
Une autre différence importante est l' endroit où dans le code les annotations peuvent être utilisées. Il existe deux approches différentes. Certains packages utilisent des contextes de style JLS 9.6.4.1. Le tableau suivant donne un aperçu:
PARAMÈTRE DE MÉTHODE DE CHAMP LOCAL_VARIABLE
android.support.annotation XXX
edu.umd.cs.findbugs.annotations XXXX
org.jetbrains.annotation XXXX
lombok XXXX
javax.validation.constraints XXX
org.eclipse.jdt.annotation
, javax.annotation
Et org.checkerframework.checker.nullness.qual
utiliser les contextes définis dans JLS 4,11, ce qui est à mon avis la bonne façon de le faire.
Cela nous laisse avec
javax.annotation
org.checkerframework.checker.nullness.qual
dans ce tour.
Code
Pour vous aider à comparer vous-même d'autres détails, j'énumère ci-dessous le code de chaque annotation. Pour faciliter la comparaison, j'ai supprimé les commentaires, les importations et l' @Documented
annotation. (ils avaient tous @Documented
sauf les classes du package Android). J'ai réorganisé les lignes et les @Target
champs et normalisé les qualifications.
package android.support.annotation;
@Retention(CLASS)
@Target({FIELD, METHOD, PARAMETER})
public @interface NonNull {}
package edu.umd.cs.findbugs.annotations;
@Retention(CLASS)
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE})
public @interface NonNull {}
package org.eclipse.jdt.annotation;
@Retention(CLASS)
@Target({ TYPE_USE })
public @interface NonNull {}
package org.jetbrains.annotations;
@Retention(CLASS)
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE})
public @interface NotNull {String value() default "";}
package javax.annotation;
@TypeQualifier
@Retention(RUNTIME)
public @interface Nonnull {
When when() default When.ALWAYS;
static class Checker implements TypeQualifierValidator<Nonnull> {
public When forConstantValue(Nonnull qualifierqualifierArgument,
Object value) {
if (value == null)
return When.NEVER;
return When.ALWAYS;
}
}
}
package org.checkerframework.checker.nullness.qual;
@Retention(RUNTIME)
@Target({TYPE_USE, TYPE_PARAMETER})
@SubtypeOf(MonotonicNonNull.class)
@ImplicitFor(
types = {
TypeKind.PACKAGE,
TypeKind.INT,
TypeKind.BOOLEAN,
TypeKind.CHAR,
TypeKind.DOUBLE,
TypeKind.FLOAT,
TypeKind.LONG,
TypeKind.SHORT,
TypeKind.BYTE
},
literals = {LiteralKind.STRING}
)
@DefaultQualifierInHierarchy
@DefaultFor({TypeUseLocation.EXCEPTION_PARAMETER})
@DefaultInUncheckedCodeFor({TypeUseLocation.PARAMETER, TypeUseLocation.LOWER_BOUND})
public @interface NonNull {}
Pour être complet, voici les @Nullable
implémentations:
package android.support.annotation;
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD})
public @interface Nullable {}
package edu.umd.cs.findbugs.annotations;
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE})
@Retention(CLASS)
public @interface Nullable {}
package org.eclipse.jdt.annotation;
@Retention(CLASS)
@Target({ TYPE_USE })
public @interface Nullable {}
package org.jetbrains.annotations;
@Retention(CLASS)
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE})
public @interface Nullable {String value() default "";}
package javax.annotation;
@TypeQualifierNickname
@Nonnull(when = When.UNKNOWN)
@Retention(RUNTIME)
public @interface Nullable {}
package org.checkerframework.checker.nullness.qual;
@Retention(RUNTIME)
@Target({TYPE_USE, TYPE_PARAMETER})
@SubtypeOf({})
@ImplicitFor(
literals = {LiteralKind.NULL},
typeNames = {java.lang.Void.class}
)
@DefaultInUncheckedCodeFor({TypeUseLocation.RETURN, TypeUseLocation.UPPER_BOUND})
public @interface Nullable {}
Les deux packages suivants n'en ont pas @Nullable
, je les énumère donc séparément; Lombok a un assez ennuyeux @NonNull
. Dans javax.validation.constraints
le @NonNull
est en fait un @NotNull
et il a une implémentation assez longue.
package lombok;
@Retention(CLASS)
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE})
public @interface NonNull {}
package javax.validation.constraints;
@Retention(RUNTIME)
@Target({ FIELD, METHOD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Constraint(validatedBy = {})
public @interface NotNull {
String message() default "{javax.validation.constraints.NotNull.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default {};
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@interface List {
NotNull[] value();
}
}
Soutien
D'après mon expérience, javax.annotation
est au moins pris en charge par Eclipse et le Checker Framework hors de la boîte.
Sommaire
Mon annotation idéale serait la java.annotation
syntaxe avec l'implémentation de Checker Framework.
Si vous n'avez pas l'intention d'utiliser le Checker Framework, le javax.annotation
( JSR-305 ) reste votre meilleur pari pour le moment.
Si vous êtes prêt à acheter dans le Checker Framework, utilisez simplement leur org.checkerframework.checker.nullness.qual
.
Sources
android.support.annotation
de android-5.1.1_r1.jar
edu.umd.cs.findbugs.annotations
de findbugs-annotations-1.0.0.jar
org.eclipse.jdt.annotation
de org.eclipse.jdt.annotation_2.1.0.v20160418-1457.jar
org.jetbrains.annotations
de jetbrains-annotations-13.0.jar
javax.annotation
de gwt-dev-2.5.1-sources.jar
org.checkerframework.checker.nullness.qual
de checker-framework-2.1.9.zip
lombok
de lombok
commitf6da35e4c4f3305ecd1b415e2ab1b9ef8a9120b4
javax.validation.constraints
de validation-api-1.0.0.GA-sources.jar