Da JSR 305 (dessen Ziel es war, zu standardisieren @NonNull
und @Nullable
) seit mehreren Jahren inaktiv ist, gibt es leider keine gute Antwort. Wir können nur eine pragmatische Lösung finden, und meine lautet wie folgt:
Syntax
Aus rein stilistischer Sicht möchte ich jegliche Bezugnahme auf IDE, Framework oder ein Toolkit außer Java selbst vermeiden.
Dies schließt aus:
android.support.annotation
edu.umd.cs.findbugs.annotations
org.eclipse.jdt.annotation
org.jetbrains.annotations
org.checkerframework.checker.nullness.qual
lombok.NonNull
Was uns entweder javax.validation.constraints
oder lässt javax.annotation
. Ersteres kommt mit JEE. Ob dies besser ist als javax.annotation
, was irgendwann mit JSE oder gar nicht kommen könnte, ist umstritten. Ich persönlich bevorzuge es, javax.annotation
weil mir die JEE-Abhängigkeit nicht gefallen würde.
Das lässt uns mit
javax.annotation
Das ist auch die kürzeste.
Es gibt nur eine Syntax, die noch besser wäre : java.annotation.Nullable
. Da andere Pakete in der Vergangenheit von javax
bis java
aufgestiegen sind, wäre die javax.annotation ein Schritt in die richtige Richtung.
Implementierung
Ich hatte gehofft, dass sie alle im Grunde die gleiche triviale Implementierung haben, aber eine detaillierte Analyse zeigte, dass dies nicht wahr ist.
Zunächst zu den Ähnlichkeiten:
Die @NonNull
Anmerkungen haben alle die Zeile
public @interface NonNull {}
ausser für
org.jetbrains.annotations
das nennt es @NotNull
und hat eine triviale Implementierung
javax.annotation
das hat eine längere Implementierung
javax.validation.constraints
das nennt es auch @NotNull
und hat eine Implementierung
Die @Nullable
Anmerkungen haben alle die Zeile
public @interface Nullable {}
mit Ausnahme (wieder) der org.jetbrains.annotations
mit ihrer trivialen Umsetzung.
Für die Unterschiede:
Auffällig ist das
javax.annotation
javax.validation.constraints
org.checkerframework.checker.nullness.qual
Alle haben Laufzeitanmerkungen ( @Retention(RUNTIME)
), während
android.support.annotation
edu.umd.cs.findbugs.annotations
org.eclipse.jdt.annotation
org.jetbrains.annotations
sind nur Kompilierzeit ( @Retention(CLASS)
).
Wie in dieser SO-Antwort beschrieben, ist die Auswirkung von Laufzeitanmerkungen geringer als man denkt, aber sie haben den Vorteil, dass Tools zusätzlich zu den Kompilierungszeitpunkten Laufzeitprüfungen durchführen können.
Ein weiterer wichtiger Unterschied besteht darin, wo im Code die Anmerkungen verwendet werden können. Es gibt zwei verschiedene Ansätze. Einige Pakete verwenden JLS 9.6.4.1-Kontexte. Die folgende Tabelle gibt einen Überblick:
FELDMETHODENPARAMETER 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
Und org.checkerframework.checker.nullness.qual
verwenden Sie die definierte Kontexte in JLS 4.11, was der richtige Weg , meiner Meinung nach ist es zu tun.
Das lässt uns mit
javax.annotation
org.checkerframework.checker.nullness.qual
in dieser Runde.
Code
Um Ihnen zu helfen, weitere Details selbst zu vergleichen, liste ich den Code jeder Anmerkung unten auf. Um den Vergleich zu erleichtern, habe ich Kommentare, Importe und @Documented
Anmerkungen entfernt. (Sie hatten alle @Documented
bis auf die Klassen aus dem Android-Paket). Ich habe die Zeilen und @Target
Felder neu angeordnet und die Qualifikationen normalisiert.
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 {}
Der Vollständigkeit halber hier die @Nullable
Implementierungen:
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 {}
Die folgenden zwei Pakete haben keine @Nullable
, daher liste ich sie separat auf. Lombok hat eine ziemlich langweilige @NonNull
. In javax.validation.constraints
der @NonNull
ist eigentlich eine @NotNull
und es hat eine längliche Umsetzung.
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();
}
}
Unterstützung
Aus meiner Erfahrung javax.annotation
wird zumindest von Eclipse und dem Checker Framework sofort unterstützt.
Zusammenfassung
Meine ideale Anmerkung wäre die java.annotation
Syntax mit der Checker Framework-Implementierung.
Wenn Sie das Checker Framework nicht verwenden möchten, ist das javax.annotation
( JSR-305 ) vorerst immer noch die beste Wahl.
Wenn Sie bereit sind, sich in das Checker Framework einzukaufen, verwenden Sie einfach deren org.checkerframework.checker.nullness.qual
.
Quellen
android.support.annotation
von android-5.1.1_r1.jar
edu.umd.cs.findbugs.annotations
von findbugs-annotations-1.0.0.jar
org.eclipse.jdt.annotation
von org.eclipse.jdt.annotation_2.1.0.v20160418-1457.jar
org.jetbrains.annotations
von jetbrains-annotations-13.0.jar
javax.annotation
von gwt-dev-2.5.1-sources.jar
org.checkerframework.checker.nullness.qual
von checker-framework-2.1.9.zip
lombok
von lombok
Commitf6da35e4c4f3305ecd1b415e2ab1b9ef8a9120b4
javax.validation.constraints
von validation-api-1.0.0.GA-sources.jar