Desde JSR 305 (dessen Ziel die Standardisierung @NonNull
y @Nullable
) seit mehreren Jahren ruht, gibt es leider keine gute Antwort. Alles, was wir tun können, ist, eine pragmatische Lösung zu finden, und meine lautet wie folgt:
Syntax
Aus rein stilistischen Gründen möchte ich jeden Verweis auf eine IDE, ein 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
Damit bleibt uns entweder javax.validation.constraints
o javax.annotation
. Ersteres wird mit JEE geliefert. Wenn dies besser ist als javax.annotation
Die Frage, ob die JSE irgendwann oder gar nicht kommen wird, ist umstritten. Ich persönlich bevorzuge javax.annotation
weil mir die JEE-Abhängigkeit nicht gefallen würde.
Damit bleibt uns
javax.annotation
die auch die kürzeste ist.
Es gibt nur eine Syntax, die noch besser wäre: java.annotation.Nullable
. Wie andere Pakete graduiert von javax
a java
in der Vergangenheit war, wäre die javax.annotation ein Schritt in die richtige Richtung sein.
Umsetzung
Ich hatte gehofft, dass sie alle im Grunde die gleiche triviale Implementierung haben, aber eine detaillierte Analyse hat gezeigt, dass dies nicht der Fall ist.
Zunächst zu den Ähnlichkeiten:
En @NonNull
Anmerkungen haben alle die Zeile
public @interface NonNull {}
mit Ausnahme von
org.jetbrains.annotations
in dem es heißt @NotNull
und hat eine triviale Implementierung
javax.annotation
die eine längere Umsetzung hat
javax.validation.constraints
die es auch als @NotNull
und hat eine Implementierung
En @Nullable
Anmerkungen haben alle die Zeile
public @interface Nullable {}
mit Ausnahme (wiederum) der org.jetbrains.annotations
mit ihrer trivialen Umsetzung.
Für die Unterschiede:
Auffallend ist, dass
javax.annotation
javax.validation.constraints
org.checkerframework.checker.nullness.qual
haben alle Laufzeit-Anmerkungen ( @Retention(RUNTIME)
), während
android.support.annotation
edu.umd.cs.findbugs.annotations
org.eclipse.jdt.annotation
org.jetbrains.annotations
sind nur zur Kompilierzeit ( @Retention(CLASS)
).
Wie beschrieben in diese SO-Antwort die Auswirkungen von Laufzeitkommentaren ist geringer als man denken könnte, aber sie haben den Vorteil dass sie Werkzeuge in die Lage versetzen, zusätzlich zu den Prüfungen zur Kompilierzeit durchzuführen.
Ein weiterer wichtiger Unterschied ist wobei im Code können die Anmerkungen verwendet werden. Es gibt zwei verschiedene Ansätze. Einige Pakete verwenden Kontexte im Stil von JLS 9.6.4.1. Die folgende Tabelle gibt einen Überblick:
Paket
FELD
METHODE
PARAMETER
LOKALE_VARIABLE
Android.support.annotation
edu.umd.cs.findbugs.annotations
org.jetbrains.annotation
Lombok
javax.validation.constraints
org.eclipse.jdt.annotation
, javax.annotation
y org.checkerframework.checker.nullness.qual
verwenden die Kontexte, die in JLS 4.11, was meiner Meinung nach der richtige Weg ist.
Dies führt zu folgenden Ergebnissen
javax.annotation
org.checkerframework.checker.nullness.qual
in dieser Runde.
Code
Um Ihnen zu helfen, weitere Details selbst zu vergleichen, führe ich unten den Code jeder Anmerkung auf. Um den Vergleich zu erleichtern, habe ich Kommentare, Importe und die @Documented
Bemerkung. (Sie alle hatten @Documented
mit Ausnahme der Klassen aus dem Android-Paket). Ich habe die Zeilen neu sortiert und @Target
Felder und normalisierte die Qualifikationen.
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 sind 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 beiden Pakete haben keine @Nullable
deshalb führe ich sie getrennt auf; Lombok hat eine ziemlich langweilige @NonNull
. Unter javax.validation.constraints
die @NonNul
ist eigentlich ein @NotNull
und die Umsetzung ist langwierig.
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 standardmäßig unterstützt.
Zusammenfassung
Meine ideale Annotation wäre die java.annotation
Syntax mit der Checker-Framework-Implementierung.
Wenn Sie nicht beabsichtigen, das Checker-Framework zu verwenden, wird die javax.annotation
( JSR-305 ) ist im Moment noch die beste Lösung.
Wenn Sie bereit sind, sich in das Checker Framework einzukaufen, verwenden Sie einfach deren org.checkerframework.checker.nullness.qual
.
Quellen
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
übergeben. f6da35e4c4f3305ecd1b415e2ab1b9ef8a9120b4
javax.validation.constraints
de validation-api-1.0.0.GA-sources.jar