Fehler beim Festlegen eines Standardnullwerts für das Feld einer Anmerkung

79

Warum erhalte ich die Fehlermeldung "Attributwert muss konstant sein". Ist nicht Null konstant ???

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SomeInterface {
    Class<? extends Foo> bar() default null;// this doesn't compile
}
ripper234
quelle
Warum sollte jemand dies benötigen (zum Kompilieren oder Umgehen)? Die gleiche "Funktionalität" wird bereits von gewährt Class<? extends Foo> bar();.
Xerx593
3
Da es keine Standardeinstellung mehr gibt, wird das Feld für die Verwendung von Anmerkungen obligatorisch
Donatello,

Antworten:

63

Ich weiß nicht warum, aber das JLS ist sehr klar:

 Discussion

 Note that null is not a legal element value for any element type. 

Und die Definition eines Standardelements lautet:

     DefaultValue:
         default ElementValue

Leider stelle ich immer wieder fest, dass die neuen Sprachfunktionen (Enums und jetzt Annotations) sehr wenig hilfreiche Compiler-Fehlermeldungen enthalten, wenn Sie die Sprachspezifikation nicht erfüllen.

EDIT: Ein wenig googleing fanden die folgende in der JSR-308, wo sie argumentieren für nulls in dieser Situation erlaubt:

Wir stellen einige mögliche Einwände gegen den Vorschlag fest.

Der Vorschlag macht nichts möglich, was vorher nicht möglich war.

Der vom Programmierer definierte Sonderwert bietet eine bessere Dokumentation als null, was "keine", "nicht initialisiert", null selbst usw. bedeuten kann.

Der Vorschlag ist fehleranfälliger. Es ist viel einfacher, die Prüfung gegen Null zu vergessen, als die Prüfung auf einen expliziten Wert zu vergessen.

Der Vorschlag kann die Standardsprache ausführlicher machen. Derzeit müssen nur die Benutzer einer Anmerkung nach ihren speziellen Werten suchen. Mit dem Vorschlag müssen viele Tools, die Anmerkungen verarbeiten, prüfen, ob der Wert eines Felds null ist, damit sie keine Nullzeigerausnahme auslösen.

Ich denke, nur die letzten beiden Punkte sind relevant für "warum nicht überhaupt?". Der letzte Punkt bringt sicherlich einen guten Punkt auf den Punkt - ein Annotationsprozessor muss sich niemals Sorgen machen, dass er eine Null für einen Annotationswert erhält. Ich neige dazu, dass es eher die Aufgabe von Annotationsprozessoren und anderem solchen Framework-Code ist, diese Art von Überprüfung durchführen zu müssen, um den Entwicklercode klarer zu machen, als umgekehrt, aber es würde es sicherlich schwierig machen, eine Änderung zu rechtfertigen.

Yishai
quelle
67

Versuche dies

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SomeInterface {
    Class bar() default void.class;
}

Es erfordert keine neue Klasse und es ist bereits ein Schlüsselwort in Java, das nichts bedeutet.

Logan Murphy
quelle
8
Ich mag das. Das einzige Problem - im Kontext der ursprünglichen Frage - ist, dass diese Antwort keine Typparameter unterstützt: Class<? extends Foo> bar() default void.null Kompiliert nicht.
Kariem
3
Die void.class kann nicht allgemein angewendet werden: public @interface NoNullDefault {String value () default void.class; }
Wjohnson
Für Groovy funktioniert es gut, auch in den in den Kommentaren genannten Fällen
Vampire
LOL: das bedeutet nichts vs das bedeutet "nichts"
Andreas
54

Es scheint, dass dies illegal ist, obwohl die JLS diesbezüglich sehr verschwommen ist.

Ich habe mein Gedächtnis zerstört, um zu versuchen, an eine vorhandene Annotation zu denken, die ein Class-Attribut für eine Annotation hatte, und mich an diese aus der JAXB-API erinnert:

@Retention(RUNTIME) @Target({PACKAGE,FIELD,METHOD,TYPE,PARAMETER})        
public @interface XmlJavaTypeAdapter {
    Class type() default DEFAULT.class;

    static final class DEFAULT {}    
}

Sie können sehen, wie sie eine statische Dummy-Klasse definieren mussten, um das Äquivalent einer Null zu halten.

Unangenehm.

Skaffman
quelle
3
Ja, wir machen etwas Ähnliches (wir verwenden nur Foo.class als Standard). Saugt.
Ripper234
7
Und im Fall von String-Feldern müssen Sie auf magische Strings zurückgreifen . Anscheinend sind bekannte Antimuster heute besser als gut verstandene Sprachmerkmale.
Tim Yates
3
@TimYates Es bringt mich um, wenn Leute ihren eigenen Sentinel-Wert "no value" definieren, wenn null verfügbar und allgemein verständlich ist. Und jetzt wird das von der Sprache selbst verlangt?! Glücklicherweise können Sie Ihre "magischen Zeichenfolgen" als öffentliche Konstanten definieren. Es ist immer noch nicht ideal, aber zumindest der Code, der nach Ihrer Anmerkung sucht, kann auf die Konstante verweisen, anstatt überall Literale zu verwenden.
spaaarky21
11

Es scheint, dass es einen anderen Weg gibt, dies zu tun.

Ich mag das auch nicht, aber es könnte funktionieren.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SomeInterface {
    Class<? extends Foo>[] bar() default {};
}

Im Grunde genommen erstellen Sie stattdessen ein leeres Array. Dies scheint einen Standardwert zuzulassen.

Shervin Asgari
quelle
3
Class<? extends Foo> bar() default null;// this doesn't compile

Wie bereits erwähnt, erlaubt die Java-Sprachspezifikation keine Nullwerte in den Annotationsstandards.

Ich neige dazu, DEFAULT_VALUEKonstanten oben in der Annotationsdefinition zu definieren. Etwas wie:

public @interface MyAnnotation {
    public static final String DEFAULT_PREFIX = "__class-name-here__ default";
    ...
    public String prefix() default DEFAULT_PREFIX;
}

Dann mache ich in meinem Code so etwas wie:

if (myAnnotation.prefix().equals(MyAnnotation.DEFAULT_PREFIX)) { ... }

In Ihrem Fall mit einer Klasse würde ich nur eine Markierungsklasse definieren. Leider können Sie dafür keine Konstante haben. Stattdessen müssen Sie Folgendes tun:

public @interface MyAnnotation {
    public static Class<? extends Foo> DEFAULT_BAR = DefaultBar.class;
    ...
    Class<? extends Foo> bar() default DEFAULT_BAR;
}

Ihre DefaultFooKlasse wäre nur eine leere Implementierung, damit der Code Folgendes tun kann:

if (myAnnotation.bar() == MyAnnotation.DEFAULT_BAR) { ... }

Hoffe das hilft.

Grau
quelle
Ich habe das übrigens nur mit einem String versucht, und das funktioniert nicht. Sie erhalten nicht dasselbe String-Objekt zurück.
Doradus
Hast du @Doradus benutzt .equals()und nicht ==? Haben Sie einen anderen Wert oder ein anderes Objekt erhalten?
Gray
Anderes Objekt. Ich habe benutzt ==.
Doradus
Nun, die Klasse ist ein Singleton @ Doradus. Ich hätte erwartet, dass der String auch ein Singleton ist, aber ich denke, das ist es nicht und Sie müssen ihn verwenden .equals(). Überraschend.
Gray
1

Diese Fehlermeldung ist irreführend, aber nein, das nullLiteral ist kein konstanter Ausdruck, wie in der Java-Sprachspezifikation hier definiert .

Darüber hinaus ist die Java Language Specification sagt

Es ist ein Fehler bei der Kompilierung, wenn der Typ des Elements nicht dem angegebenen Standardwert entspricht (§9.7).

Und um zu erklären, was das bedeutet

Es ist ein Fehler bei der Kompilierung, wenn der Elementtyp nicht dem Elementwert entspricht. Ein Elementtyp T entspricht genau dann einem Elementwert V, wenn eine der folgenden Bedingungen erfüllt ist:

  • Tist ein Array-Typ E[]und entweder:
    • [...]
  • Tkeine Array - Typ , und die Art der VZuweisung kompatibel ist (§5.2) mit T, und :
    • Wenn Tes sich um einen primitiven Typ handelt String, Vhandelt es sich um einen konstanten Ausdruck (§15.28).
    • Wenn Tist Classoder ein Aufruf von Class(§4.5), dann Vist ein Klassenliteral (§15.8.2).
    • Wenn Tes sich um einen Aufzählungstyp handelt (§8.9), dann Vhandelt es sich um eine Aufzählungskonstante (§8.9.1).
    • V ist nicht null .

Hier entspricht Ihr Elementtyp Class<? extends Foo>also nicht dem Standardwert, da dieser Wert ist null.

Sotirios Delimanolis
quelle
Ihre Lösung ist nicht zum Kopieren und Einfügen geeignet ;-) Einige denken beim Lesen nicht gern
Matthias M
1
IMHO ja, du hast es getan (aber ich war es nicht). Ihr Ausdruck lautet wie folgt: "wenn (...) oder (V ist nicht null)", was so klingt, als wäre ein Wert ungleich Null richtig. Die JLS-Formulierung ist ein echtes Biest, aber es gibt ein Äußeres orund ein Inneres and, die nicht ausgelassen werden dürfen.
Maaartinus
@maaartinus Hatte Ihren Kommentar von vor langer Zeit noch nicht gesehen und meine Antwort aktualisiert, um das gesamte Zitat hinzuzufügen.
Sotirios Delimanolis
1

Wie andere gesagt haben, gibt es eine Regel, die besagt, dass null in Java kein gültiger Standardwert ist.

Wenn Sie eine begrenzte Anzahl möglicher Werte für das Feld haben , können Sie dies umgehen, indem Sie den Feldtyp zu einer benutzerdefinierten Aufzählung machen, die selbst eine Zuordnung zu null enthält. Stellen Sie sich zum Beispiel vor, ich habe die folgende Anmerkung, für die ich eine Standard-Null haben möchte:

public @interface MyAnnotation {
   Class<?> value() default null;
}

Wie wir festgestellt haben, ist dies nicht zulässig. Was wir stattdessen tun können, ist eine Aufzählung zu definieren:

public enum MyClassEnum {
    NULL(null),
    MY_CLASS1(MyClass1.class),
    MY_CLASS2(MyClass2.class),
    ;

    public final Class<?> myClass;

    MyClassEnum(Class<?> aClass) {
        myClass = aClass;
    }
}

und ändern Sie die Anmerkung als solche:

public @interface MyAnnotation {
  MyClassEnum value() default MyClassEnum.NULL;
}

Der Hauptnachteil davon (abgesehen davon, dass Sie Ihre möglichen Werte aufzählen müssen) ist, dass Sie Ihren Wert jetzt in eine Aufzählung eingeschlossen haben, sodass Sie die Eigenschaft / den Getter in der Aufzählung aufrufen müssen, um den Wert zu erhalten.

AMTerp
quelle