Błąd podczas ustawiania domyślnej wartości null dla pola adnotacji

79

Dlaczego pojawia się błąd „Wartość atrybutu musi być stała”. Czy wartość null nie jest stała ???

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SomeInterface {
    Class<? extends Foo> bar() default null;// this doesn't compile
}
zrywak234
źródło
Dlaczego ktoś miałby to potrzebować (do kompilacji lub obejścia)? Ta sama „funkcjonalność” jest już przyznana przez Class<? extends Foo> bar();.
xerx593
3
Ponieważ nie ma już wartości domyślnych, pole to staje się obowiązkowe w przypadku używania adnotacji
Donatello

Odpowiedzi:

64

Nie wiem dlaczego, ale JLS jest bardzo jasne:

 Discussion

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

Definicja elementu domyślnego to:

     DefaultValue:
         default ElementValue

Niestety, wciąż odkrywam, że nowe funkcje językowe (wyliczenia, a teraz adnotacje) mają bardzo nieprzydatne komunikaty o błędach kompilatora, gdy nie spełniają one specyfikacji języka.

EDYCJA: Trochę googleing znalazło następujące w JSR-308, gdzie argumentują za dopuszczeniem wartości null w tej sytuacji:

Zwracamy uwagę na możliwe zastrzeżenia do wniosku.

Propozycja nie umożliwia niczego, co nie było możliwe wcześniej.

Zdefiniowana przez programistę wartość specjalna zapewnia lepszą dokumentację niż wartość null, co może oznaczać „brak”, „niezainicjowany”, samą wartość zerową itp.

Oferta pakietowa jest bardziej podatna na błędy. O wiele łatwiej jest zapomnieć o sprawdzaniu wartości null, niż o sprawdzaniu jawnej wartości.

Propozycja może sprawić, że standardowy idiom będzie bardziej rozwlekły. Obecnie tylko użytkownicy adnotacji muszą sprawdzać jej specjalne wartości. Dzięki tej propozycji wiele narzędzi przetwarzających adnotacje będzie musiało sprawdzić, czy wartość pola jest równa zeru, aby nie zgłosić wyjątku wskaźnika zerowego.

Myślę, że tylko dwa ostatnie punkty są istotne dla „dlaczego nie zrobić tego w pierwszej kolejności”. Ostatni punkt z pewnością prowadzi do dobrego punktu - procesor adnotacji nigdy nie musi się martwić, że otrzyma wartość null w wartości adnotacji. Zwykle postrzegam to jako bardziej zadanie procesorów adnotacji i innego takiego kodu ramowego, aby wykonać tego rodzaju sprawdzenie, aby kod programistów był bardziej przejrzysty niż na odwrót, ale z pewnością utrudniłoby to uzasadnienie zmiany.

Yishai
źródło
67

Spróbuj tego

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

Nie wymaga nowej klasy i jest już słowem kluczowym w Javie, które nic nie znaczy.

Logan Murphy
źródło
8
Lubię to. Jedynym problemem - w kontekście pierwotnego pytania - jest to, że ta odpowiedź nie obsługuje parametrów typu: Class<? extends Foo> bar() default void.null nie kompiluje się.
Kariem
3
Generalnie nie można zastosować void.class: public @interface NoNullDefault {String value () default void.class; }
wjohnson,
W przypadku Groovy działa dobrze, nawet w przypadkach wymienionych w komentarzach
Vampire
LOL: to nic nie znaczy vs to znaczy „nic”
Andreas
55

Wydawałoby się, że jest to nielegalne, chociaż JLS jest w tym bardzo niejasny.

Zerwałem pamięć, aby spróbować pomyśleć o istniejącej adnotacji, która miała atrybut Class do adnotacji, i zapamiętałem tę z interfejsu API JAXB:

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

    static final class DEFAULT {}    
}

Możesz zobaczyć, jak musieli zdefiniować fikcyjną klasę statyczną, aby przechowywać odpowiednik wartości null.

Nieprzyjemny.

skaffman
źródło
3
Tak, robimy coś podobnego (domyślnie używamy po prostu Foo.class). Do bani.
ripper234
7
A w przypadku pól String musisz uciekać się do magicznych strun . Najwyraźniej dobrze znane antywzory są teraz lepsze niż dobrze rozumiane cechy języka.
Tim Yates
3
@TimYates Zabija mnie widok ludzi definiujących własną wartość wartowniczą „bez wartości”, gdy wartość null jest dostępna i powszechnie rozumiana. A teraz wymaga tego sam język ?! Na szczęście możesz zdefiniować swoje „magiczne ciągi” jako publiczne stałe. Nadal nie jest to idealne rozwiązanie, ale przynajmniej kod, który szuka Twojej adnotacji, może odwoływać się do stałej zamiast wszędzie używać literałów.
spaaarky21
11

Wygląda na to, że jest na to inny sposób.

To też mi się nie podoba, ale może zadziałać.

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

Więc w zasadzie zamiast tego tworzysz pustą tablicę. Wydaje się, że zezwala na wartość domyślną.

Shervin Asgari
źródło
3
Class<? extends Foo> bar() default null;// this doesn't compile

Jak wspomniano, specyfikacja języka Java nie zezwala na wartości null w domyślnych wartościach adnotacji.

To, co zwykle robię, to definiowanie DEFAULT_VALUEstałych u góry definicji adnotacji. Coś jak:

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

Następnie w swoim kodzie robię coś takiego:

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

W twoim przypadku z klasą zdefiniowałbym po prostu klasę markera. Niestety nie możesz mieć dla tego stałej, więc zamiast tego musisz zrobić coś takiego:

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

Twoja DefaultFooklasa byłaby po prostu pustą implementacją, aby kod mógł:

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

Mam nadzieję że to pomoże.

Szary
źródło
Nawiasem mówiąc, właśnie wypróbowałem to ze stringiem i to nie działa. Nie odzyskasz tego samego obiektu String.
Doradus
Czy używałeś, .equals()a nie ==@Doradus? Czy otrzymałeś inną wartość lub inny przedmiot.
Gray
Inny przedmiot. Użyłem ==.
Doradus,
Cóż, klasa to singleton @Doradus. Spodziewałbym się, że String również będzie singletonem, ale myślę, że tak nie jest i będziesz musiał użyć .equals(). Zaskakujący.
Gray
1

Ten komunikat o błędzie jest mylący, ale nie, nullliterał nie jest wyrażeniem stałym, zgodnie z definicją w specyfikacji języka Java, tutaj .

Co więcej, specyfikacja języka Java mówi

Jest to błąd czasu kompilacji, jeśli typ elementu nie jest współmierny (§9.7) z określoną wartością domyślną.

I wyjaśnić, co to oznacza

Jest to błąd czasu kompilacji, jeśli typ elementu nie jest współmierny do wartości elementu. Element typu T jest współmierny do wartości elementu V wtedy i tylko wtedy, gdy spełniony jest jeden z poniższych warunków:

  • T jest typem tablicy E[] i albo:
    • […]
  • Tnie jest typem tablicy , a typ Vprzypisania jest zgodny (§5.2) z T, i :
    • Jeśli Tjest typem pierwotnym lub String, to Vjest wyrażeniem stałym (§15.28).
    • Jeśli Tjest Classlub przywołanie Class(§4.5), to Vjest literałem klasy (§15.8.2).
    • Jeśli Tjest typem wyliczenia (§8.9), to Vjest stałą wyliczeniową (§8.9.1).
    • V nie jest zerowe .

Więc tutaj twój typ elementu Class<? extends Foo>nie jest współmierny z wartością domyślną, ponieważ ta wartość to null.

Sotirios Delimanolis
źródło
Twoje rozwiązanie nie nadaje się do kopiowania i wklejania ;-) Niektórzy nie lubią myśleć podczas czytania
Matthias M
1
IMHO tak, zrobiłeś (ale to nie byłem ja). Twoje wyrażenie brzmi jak „when (...) lub (V nie jest null)”, co brzmi tak, jakby każda wartość inna niż null byłaby właściwa. Formuła JLS to prawdziwa bestia, ale jest to, co zewnętrzne ori wewnętrzne and, których nie można pominąć.
maaartinus
@maaartinus Dawno nie widziałem twojego komentarza, zaktualizowałem moją odpowiedź, dodając cały cytat.
Sotirios Delimanolis
1

Jak powiedzieli inni, istnieje reguła, która mówi, że null nie jest prawidłową wartością domyślną w Javie.

Jeśli masz ograniczoną liczbę możliwych wartości, które może mieć pole , jedną z opcji obejścia tego problemu jest ustawienie typu pola jako niestandardowego wyliczenia, które samo zawiera mapowanie na null. Na przykład wyobraź sobie, że mam następującą adnotację, dla której chcę domyślnej wartości null:

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

Jak ustaliliśmy, jest to niedozwolone. Zamiast tego możemy zdefiniować wyliczenie:

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

    public final Class<?> myClass;

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

i zmień adnotację jako taką:

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

Główną wadą tego (oprócz konieczności wyliczenia możliwych wartości) jest to, że teraz opakowałeś swoją wartość w wyliczenie, więc musisz wywołać właściwość / getter na wyliczeniu, aby uzyskać wartość.

AMTerp
źródło