Depuis JSR 305 (dont l'objectif était de normaliser @NonNullet @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.constraintsou 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.annotationparce 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à javadans 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 @NonNullannotations ont toutes la ligne
public @interface NonNull {}
à l'exception de
org.jetbrains.annotationsqui l'appelle @NotNullet a une implémentation triviale
javax.annotation qui a une mise en œuvre plus longue
javax.validation.constraintsqui l'appelle aussi @NotNullet a une implémentation
Les @Nullableannotations ont toutes la ligne
public @interface Nullable {}
sauf (encore) le org.jetbrains.annotationsavec 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.annotationEt org.checkerframework.checker.nullness.qualutiliser 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' @Documentedannotation. (ils avaient tous @Documentedsauf les classes du package Android). J'ai réorganisé les lignes et les @Targetchamps 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 @Nullableimplé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.constraintsle @NonNullest 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.annotationest au moins pris en charge par Eclipse et le Checker Framework hors de la boîte.
Sommaire
Mon annotation idéale serait la java.annotationsyntaxe 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
lombokde lombokcommitf6da35e4c4f3305ecd1b415e2ab1b9ef8a9120b4
javax.validation.constraints de validation-api-1.0.0.GA-sources.jar