Como o JSR 305 (cujo objetivo era padronizar @NonNull
e @Nullable
) permanece inativo por vários anos, receio que não haja uma boa resposta. Tudo o que podemos fazer é encontrar uma solução pragmática e a minha é a seguinte:
Sintaxe
Do ponto de vista puramente estilístico, eu gostaria de evitar qualquer referência ao IDE, framework ou qualquer kit de ferramentas, exceto o próprio Java.
Isso exclui:
android.support.annotation
edu.umd.cs.findbugs.annotations
org.eclipse.jdt.annotation
org.jetbrains.annotations
org.checkerframework.checker.nullness.qual
lombok.NonNull
O que nos deixa com javax.validation.constraints
ou javax.annotation
. O primeiro vem com o JEE. Se isso é melhor do que javax.annotation
, o que pode vir eventualmente com o JSE ou nunca, é uma questão de debate. Pessoalmente, prefiro javax.annotation
porque não gostaria da dependência do JEE.
Isso nos deixa com
javax.annotation
que também é o mais curto.
Há apenas uma sintaxe que seria ainda melhor: java.annotation.Nullable
. Como outros pacotes passaram javax
para java
o passado, a anotação javax.an seria um passo na direção certa.
Implementação
Eu esperava que todos eles tivessem basicamente a mesma implementação trivial, mas uma análise detalhada mostrou que isso não é verdade.
Primeiro pelas semelhanças:
Todas as @NonNull
anotações têm a linha
public @interface NonNull {}
exceto por
org.jetbrains.annotations
que chama @NotNull
e tem uma implementação trivial
javax.annotation
que tem uma implementação mais longa
javax.validation.constraints
que também chama @NotNull
e tem uma implementação
Todas as @Nullable
anotações têm a linha
public @interface Nullable {}
exceto (novamente) o org.jetbrains.annotations
com sua implementação trivial.
Para as diferenças:
Um impressionante é que
javax.annotation
javax.validation.constraints
org.checkerframework.checker.nullness.qual
todos têm anotações em tempo de execução ( @Retention(RUNTIME)
), enquanto
android.support.annotation
edu.umd.cs.findbugs.annotations
org.eclipse.jdt.annotation
org.jetbrains.annotations
são apenas tempo de compilação ( @Retention(CLASS)
).
Conforme descrito nesta resposta do SO, o impacto das anotações em tempo de execução é menor do que se imagina, mas elas têm o benefício de permitir que as ferramentas realizem verificações em tempo de execução, além das de tempo de compilação.
Outra diferença importante é onde no código as anotações podem ser usadas. Existem duas abordagens diferentes. Alguns pacotes usam contextos de estilo JLS 9.6.4.1. A tabela a seguir fornece uma visão geral:
PARÂMETRO DO MÉTODO DE CAMPO 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
E org.checkerframework.checker.nullness.qual
usar os contextos definidos no JLS 4,11, o que na minha opinião é o caminho certo para fazê-lo.
Isso nos deixa com
javax.annotation
org.checkerframework.checker.nullness.qual
nesta rodada.
Código
Para ajudá-lo a comparar mais detalhes, listo o código de todas as anotações abaixo. Para facilitar a comparação, removi comentários, importações e @Documented
anotações. (todos eles tinham, @Documented
exceto as classes do pacote Android). Reordenei as linhas e os @Target
campos e normalizei as qualificações.
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, aqui estão as @Nullable
implementações:
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 {}
Os dois pacotes a seguir não têm @Nullable
, então eu os listo separadamente; Lombok é muito chato @NonNull
. Em javax.validation.constraints
o @NonNull
é na verdade um @NotNull
e tem uma implementação comprido.
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();
}
}
Apoio, suporte
Pela minha experiência, javax.annotation
é pelo menos suportado pelo Eclipse e pelo Checker Framework pronto para uso.
Sumário
Minha anotação ideal seria a java.annotation
sintaxe com a implementação do Checker Framework.
Se você não pretende usar o Checker Framework, o javax.annotation
( JSR-305 ) ainda é sua melhor aposta no momento.
Se você deseja comprar o Checker Framework, basta usá-lo org.checkerframework.checker.nullness.qual
.
Fontes
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
confirmarf6da35e4c4f3305ecd1b415e2ab1b9ef8a9120b4
javax.validation.constraints
de validation-api-1.0.0.GA-sources.jar