Помилка встановлення нульового значення за замовчуванням для поля анотації


79

Чому я отримую повідомлення про помилку "Значення атрибута має бути постійним". Чи не є нульовою константою ???

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SomeInterface {
    Class<? extends Foo> bar() default null;// this doesn't compile
}

Навіщо комусь це потрібно (для компіляції чи обхідного шляху)? Той самий "функціонал" вже наданий Class<? extends Foo> bar();.
xerx593

3
Оскільки за замовчуванням більше не існує, поле стає обов’язковим для використання анотацій
Донателло,

Відповіді:


64

Не знаю чому, але JLS дуже чітко:

 Discussion

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

І визначення елемента за замовчуванням:

     DefaultValue:
         default ElementValue

На жаль, я постійно виявляю, що нові мовні функції (Enums та тепер Анотації) мають дуже корисні повідомлення про помилки компілятора, коли ви не відповідаєте специфікаціям мови.

РЕДАГУВАТИ: Трохи погугливши, знайшов у JSR-308 наступне , де вони аргументують допущення нулів у цій ситуації:

Ми відзначаємо кілька можливих заперечень проти пропозиції.

Пропозиція не робить нічого можливим, що не було можливим раніше.

Спеціальне значення, визначене програмістом, забезпечує кращу документацію, ніж null, що може означати «немає», «неініціалізовано», саме null тощо.

Пропозиція більш схильна до помилок. Набагато простіше забути перевірку проти нуля, ніж забути перевірку на явне значення.

Пропозиція може зробити стандартну ідіому більш багатослівною. Наразі лише користувачам анотації потрібно перевіряти її спеціальні значення. З цією пропозицією багатьом інструментам, які обробляють анотації, доведеться перевірити, чи значення поля є нульовим, щоб вони не викидали виняток нульового вказівника.

Я думаю, що лише два останні пункти мають відношення до "чому б це не зробити в першу чергу". Останній пункт, безумовно, підкреслює хороший момент - процесор анотацій ніколи не повинен турбуватися про те, що вони отримають нуль для значення анотації. Я схильний бачити, що, як більше робота процесорів анотацій та іншого подібного фреймворкового коду, потрібно робити таку перевірку, щоб зробити код розробників зрозумілішим, а не навпаки, але це, безумовно, ускладнить виправдання його зміни.


67

Спробуйте це

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

Для цього не потрібен новий клас, і це вже ключове слово в Java, яке нічого не означає.


8
Мені це подобається. Єдина проблема - в контексті вихідного питання - полягає в тому, що ця відповідь не підтримує параметри типу: Class<? extends Foo> bar() default void.null не компілюється.
Kariem

3
Void.class не можна застосувати загалом: public @interface NoNullDefault {Значення рядка () за замовчуванням void.class; }
wjohnson

Для Groovy це чудово працює, навіть у випадках, згаданих у коментарях
Vampire

LOL: це нічого не означає проти того, що означає "нічого"
Андреас,

55

Здавалося б, це незаконно, хоча JLS дуже нечіткий щодо цього.

Я спонукав пам’ять, щоб спробувати подумати про наявну анотацію, яка мала атрибут Class для анотації, і запам’ятав цю з API JAXB:

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

    static final class DEFAULT {}    
}

Ви можете бачити, як їм довелося визначити фіктивний статичний клас, щоб утримати еквівалент нуля.

Неприємно.


3
Так, ми робимо щось подібне (ми просто використовуємо Foo.class за замовчуванням). Відстій.
ripper234

7
А у випадку з полями Рядок, вам доведеться вдатися до магічних струн . Мабуть, добре відомі антишаблони краще, ніж добре зрозумілі мовні особливості зараз.
Тім Йейтс

3
@TimYates Мене вбиває, коли я бачу, як люди визначають власне дозорне значення "не має значення", коли значення null доступне та загально зрозуміле. А тепер цього вимагає сама мова ?! На щастя, ви можете визначити свої "магічні струни" як загальнодоступні константи. Це все ще не ідеально, але принаймні код, який шукає вашу анотацію, може посилатися на константу, а не використовувати літерали скрізь.
spaaarky21

11

Здається, існує ще один спосіб зробити це.

Мені це теж не подобається, але це може спрацювати.

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

Отже, в основному замість цього ви створюєте порожній масив. Здається, це дозволяє значення за замовчуванням.


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

Як уже згадувалося, специфікація мови Java не допускає нульових значень за замовчуванням анотацій.

Що я прагну зробити, це зробити, це визначити DEFAULT_VALUEконстанти у верхній частині визначення анотації. Щось на зразок:

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

Потім у своєму коді я роблю щось на зразок:

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

У вашому випадку з класом я б просто визначив клас маркера. На жаль, у вас не може бути константи для цього, тому натомість вам потрібно зробити щось на зразок:

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

Ваш DefaultFooклас буде просто порожньою реалізацією, щоб код міг робити:

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

Сподіваюся, це допомагає.


До речі, я просто спробував це за допомогою рядка, і це не працює. Ви не отримаєте назад той самий об'єкт String.
Дорадус

Ви використовували, .equals()а не ==@Doradus? Ви отримали інше значення чи інший предмет.
Сірий

Інший об'єкт. Я використовував ==.
Дорадус

Ну клас - це синглтон @Doradus. Я б очікував, що String також буде одностороннім, але я думаю, що це не так, і вам потрібно буде використовувати .equals(). Дивно.
Грей

1

Це повідомлення про помилку вводить в оману, але, ні, nullлітерал не є постійним виразом, як визначено в Специфікації мови Java, тут .

Більше того, сказано в специфікації мови Java

Помилка під час компіляції, якщо тип елемента не співмірний (§9.7) із зазначеним значенням за замовчуванням.

І пояснити, що це означає

Помилка під час компіляції, якщо тип елемента не співмірний із значенням елемента. Тип елемента T співмірний зі значенням елемента V тоді і тільки тоді, коли є одним із наступних істинним:

  • T - тип масиву E[] , а також:
    • [...]
  • Tне є типом масиву , а тип Vсумісний з присвоєнням (§5.2) з T, та :
    • Якщо Tце примітивний тип або String, то Vце постійний вираз (§15.28).
    • Якщо Tє Classабо виклик Class(§4.5), то Vце літерал класу (§15.8.2).
    • Якщо Tце тип переліку (§8.9), то Vце константа переліку (§8.9.1).
    • V не дорівнює нулю .

Отже, тут ваш тип елемента Class<? extends Foo>,, не співмірний із значенням за замовчуванням, оскільки це значення є null.


Ваше рішення не підходить для копіювання та вставлення ;-) Деякі не люблять думати під час читання
Маттіас М

1
ІМХО, так, ти зробив (але це був не я). Ваш вираз читається як "коли (...) або (V не має значення null)", що звучить як будь-яке ненульове значення буде правильним. Формулювання JLS - справжній звір, але є зовнішній orі внутрішній and, які не можна залишати поза увагою.
maaartinus

@maaartinus Не бачив вашого давнього коментаря, оновив мою відповідь, щоб додати цілу цитату.
Sotirios

1

Як вже говорили інші, існує правило, яке говорить, що null не є дійсним значенням за замовчуванням у Java.

Якщо у вас обмежена кількість можливих значень, які може мати поле , тоді одним із варіантів для обходу цього є зробити тип поля користувацьким переліченням, яке саме містить відображення в нуль. Наприклад, уявіть, що у мене є така анотація, для якої я хочу нульове значення за замовчуванням:

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

Як ми встановили, це заборонено. Замість цього ми можемо визначити перелік:

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

    public final Class<?> myClass;

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

і змінити анотацію як таку:

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

Основним недоліком цього (крім необхідності перерахувати ваші можливі значення) є те, що ви тепер обернули своє значення в перелічення, тому вам потрібно буде викликати властивість / getter в переліку, щоб отримати значення.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.