Ponieważ JSR 305 (którego celem była standaryzacja @NonNull
i @Nullable
) był nieaktywny od kilku lat, obawiam się, że nie ma dobrej odpowiedzi. Wszystko, co możemy zrobić, to znaleźć pragmatyczne rozwiązanie, a moje jest następujące:
Składnia
Z czysto stylistycznego punktu widzenia chciałbym uniknąć jakiegokolwiek odniesienia do IDE, frameworka lub jakiegokolwiek zestawu narzędzi oprócz samego Java.
Wyklucza to:
android.support.annotation
edu.umd.cs.findbugs.annotations
org.eclipse.jdt.annotation
org.jetbrains.annotations
org.checkerframework.checker.nullness.qual
lombok.NonNull
Który pozostawia nam albo javax.validation.constraints
albo javax.annotation
. Ten pierwszy pochodzi z JEE. Jeśli jest to lepsze niż javax.annotation
, co może ostatecznie przynieść JSE lub wcale, jest kwestią do dyskusji. Osobiście wolę, javax.annotation
ponieważ nie chciałbym zależności JEE.
To nas zostawia
javax.annotation
który jest również najkrótszy.
Jest tylko jeden składnia co byłoby jeszcze lepiej: java.annotation.Nullable
. Jak inne pakiety ukończył javax
aby java
w przeszłości javax.annotation byłby to krok we właściwym kierunku.
Realizacja
Miałem nadzieję, że wszystkie mają w zasadzie tę samą trywialną implementację, ale szczegółowa analiza wykazała, że to nieprawda.
Najpierw podobieństwa:
Wszystkie @NonNull
adnotacje mają linię
public @interface NonNull {}
z wyjątkiem
org.jetbrains.annotations
który to nazywa @NotNull
i ma trywialną implementację
javax.annotation
który ma dłuższą implementację
javax.validation.constraints
który również to nazywa @NotNull
i ma implementację
Wszystkie @Nullable
adnotacje mają linię
public @interface Nullable {}
z wyjątkiem (ponownie) org.jetbrains.annotations
z ich trywialną implementacją.
Różnice:
Uderzające jest to
javax.annotation
javax.validation.constraints
org.checkerframework.checker.nullness.qual
wszystkie mają adnotacje środowiska wykonawczego ( @Retention(RUNTIME)
), podczas gdy
android.support.annotation
edu.umd.cs.findbugs.annotations
org.eclipse.jdt.annotation
org.jetbrains.annotations
są tylko czasem kompilacji ( @Retention(CLASS)
).
Jak opisano w tej odpowiedzi SO, wpływ adnotacji środowiska wykonawczego jest mniejszy, niż mogłoby się wydawać, ale mają one tę zaletę, że oprócz narzędzi do kompilacji mają także możliwość przeprowadzania kontroli środowiska wykonawczego.
Inną ważną różnicą jest to, gdzie w kodzie można używać adnotacji. Istnieją dwa różne podejścia. Niektóre pakiety używają kontekstów w stylu JLS 9.6.4.1. Poniższa tabela zawiera przegląd:
PARAMETR METODY POLA 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
I org.checkerframework.checker.nullness.qual
użyć konteksty określonych w JLS 4.11, co jest moim zdaniem dobry sposób, żeby to zrobić.
To nas zostawia
javax.annotation
org.checkerframework.checker.nullness.qual
w tej rundzie.
Kod
Aby pomóc Ci porównać dalsze szczegóły, poniżej zamieszczam kod każdej adnotacji. Aby ułatwić porównywanie, usunąłem komentarze, import i @Documented
adnotacje. (wszyscy mieli @Documented
oprócz zajęć z pakietu Androida). Zmieniłem kolejność linii i @Target
pól oraz znormalizowałem kwalifikacje.
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 {}
Dla kompletności, oto @Nullable
implementacje:
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 {}
Poniższe dwa pakiety nie mają @Nullable
, więc wymieniam je osobno; Lombok jest dość nudny @NonNull
. W jest w rzeczywistości
i ma dość długi wdrożenia.javax.validation.constraints
@NonNull
@NotNull
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();
}
}
Wsparcie
Z mojego doświadczenia javax.annotation
wynika , że jest co najmniej obsługiwany przez Eclipse i Checker Framework od razu po wyjęciu z pudełka.
Podsumowanie
Moją idealną adnotacją byłaby java.annotation
składnia z implementacją Checker Framework.
Jeśli nie zamierzasz korzystać z Checker Framework, javax.annotation
( JSR-305 ) jest na razie najlepszym wyborem.
Jeśli chcesz kupić w Checker Framework, skorzystaj z nich org.checkerframework.checker.nullness.qual
.
Źródła
android.support.annotation
od android-5.1.1_r1.jar
edu.umd.cs.findbugs.annotations
od findbugs-annotations-1.0.0.jar
org.eclipse.jdt.annotation
od org.eclipse.jdt.annotation_2.1.0.v20160418-1457.jar
org.jetbrains.annotations
od jetbrains-annotations-13.0.jar
javax.annotation
od gwt-dev-2.5.1-sources.jar
org.checkerframework.checker.nullness.qual
od checker-framework-2.1.9.zip
lombok
z lombok
zatwierdzeniaf6da35e4c4f3305ecd1b415e2ab1b9ef8a9120b4
javax.validation.constraints
od validation-api-1.0.0.GA-sources.jar