Da JSR 305 (dessen Ziel es war, zu standardisieren @NonNullund @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.constraintsoder 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.annotationweil 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 javaxbis javaaufgestiegen 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 @NonNullAnmerkungen haben alle die Zeile
public @interface NonNull {}
ausser für
org.jetbrains.annotationsdas nennt es @NotNullund hat eine triviale Implementierung
javax.annotation das hat eine längere Implementierung
javax.validation.constraintsdas nennt es auch @NotNullund hat eine Implementierung
Die @NullableAnmerkungen haben alle die Zeile
public @interface Nullable {}
mit Ausnahme (wieder) der org.jetbrains.annotationsmit 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.annotationUnd org.checkerframework.checker.nullness.qualverwenden 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 @DocumentedAnmerkungen entfernt. (Sie hatten alle @Documentedbis auf die Klassen aus dem Android-Paket). Ich habe die Zeilen und @TargetFelder 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 @NullableImplementierungen:
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.constraintsder @NonNullist 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.annotationwird zumindest von Eclipse und dem Checker Framework sofort unterstützt.
Zusammenfassung
Meine ideale Anmerkung wäre die java.annotationSyntax 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
lombokvon lombokCommitf6da35e4c4f3305ecd1b415e2ab1b9ef8a9120b4
javax.validation.constraints von validation-api-1.0.0.GA-sources.jar