Dado que JSR 305 (cuyo objetivo era estandarizar @NonNull
y @Nullable
) ha estado inactivo durante varios años, me temo que no hay una buena respuesta. Todo lo que podemos hacer es encontrar una solución pragmática y la mía es la siguiente:
Sintaxis
Desde un punto de vista puramente estilístico, me gustaría evitar cualquier referencia a IDE, framework o cualquier kit de herramientas, excepto Java.
Esto descarta:
android.support.annotation
edu.umd.cs.findbugs.annotations
org.eclipse.jdt.annotation
org.jetbrains.annotations
org.checkerframework.checker.nullness.qual
lombok.NonNull
Lo que nos deja con javax.validation.constraints
o javax.annotation
. El primero viene con JEE. Si esto es mejor que javax.annotation
, lo que podría ocurrir con JSE o nunca, es un tema de debate. Personalmente prefiero javax.annotation
porque no me gustaría la dependencia JEE.
Esto nos deja con
javax.annotation
que es también el más corto
Sólo hay una sintaxis que incluso puede ser mejor: java.annotation.Nullable
. A medida que otros paquetes se graduaron de javax
a java
en el pasado, la anotación javax sería un paso en la dirección correcta.
Implementación
Esperaba que todos tuvieran básicamente la misma implementación trivial, pero un análisis detallado mostró que esto no es cierto.
Primero por las similitudes:
Las @NonNull
anotaciones todos tienen la línea
public @interface NonNull {}
excepto por
org.jetbrains.annotations
que lo llama @NotNull
y tiene una implementación trivial
javax.annotation
que tiene una implementación más larga
javax.validation.constraints
que también lo llama @NotNull
y tiene una implementación
Las @Nullable
anotaciones todos tienen la línea
public @interface Nullable {}
a excepción de (nuevamente) org.jetbrains.annotations
con su implementación trivial.
Por las diferencias:
Una sorprendente es que
javax.annotation
javax.validation.constraints
org.checkerframework.checker.nullness.qual
todos tienen anotaciones de tiempo de ejecución ( @Retention(RUNTIME)
), mientras que
android.support.annotation
edu.umd.cs.findbugs.annotations
org.eclipse.jdt.annotation
org.jetbrains.annotations
son solo tiempo de compilación ( @Retention(CLASS)
).
Como se describe en esta respuesta SO, el impacto de las anotaciones de tiempo de ejecución es menor de lo que uno podría pensar, pero tienen el beneficio de permitir que las herramientas realicen comprobaciones de tiempo de ejecución además de las de tiempo de compilación.
Otra diferencia importante es dónde se pueden usar las anotaciones en el código. Hay dos enfoques diferentes. Algunos paquetes usan contextos de estilo JLS 9.6.4.1. La siguiente tabla ofrece una descripción general:
MÉTODO DE CAMPO PARÁMETRO VARIABLE LOCAL
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
y org.checkerframework.checker.nullness.qual
use los contextos definidos en JLS 4.11, que en mi opinión es la forma correcta de hacerlo.
Esto nos deja con
javax.annotation
org.checkerframework.checker.nullness.qual
en esta ronda
Código
Para ayudarlo a comparar más detalles usted mismo, enumero el código de cada anotación a continuación. Para facilitar la comparación, eliminé los comentarios, las importaciones y la @Documented
anotación. (todos tenían a @Documented
excepción de las clases del paquete de Android). Reordené las líneas y los @Target
campos y normalicé las calificaciones.
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 {}
Para completar, aquí están las @Nullable
implementaciones:
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 {}
Los siguientes dos paquetes no tienen @Nullable
, así que los enumero por separado; Lombok tiene una muy aburrida @NonNull
. En javax.validation.constraints
la @NonNull
realidad es una @NotNull
y tiene una aplicación bastante largo.
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();
}
}
Apoyo
Desde mi experiencia, javax.annotation
al menos es compatible con Eclipse y el Checker Framework listo para usar.
Resumen
Mi anotación ideal sería la java.annotation
sintaxis con la implementación de Checker Framework.
Si no tiene la intención de utilizar el Checker Framework, el javax.annotation
( JSR-305 ) sigue siendo su mejor apuesta por el momento.
Si está dispuesto a comprar el Checker Framework, simplemente use su org.checkerframework.checker.nullness.qual
.
Fuentes
android.support.annotation
desde android-5.1.1_r1.jar
edu.umd.cs.findbugs.annotations
desde findbugs-annotations-1.0.0.jar
org.eclipse.jdt.annotation
desde org.eclipse.jdt.annotation_2.1.0.v20160418-1457.jar
org.jetbrains.annotations
desde jetbrains-annotations-13.0.jar
javax.annotation
desde gwt-dev-2.5.1-sources.jar
org.checkerframework.checker.nullness.qual
desde checker-framework-2.1.9.zip
lombok
de lombok
commitf6da35e4c4f3305ecd1b415e2ab1b9ef8a9120b4
javax.validation.constraints
desde validation-api-1.0.0.GA-sources.jar