Ошибка при установке нулевого значения по умолчанию для поля аннотации

79

Почему я получаю сообщение об ошибке «Значение атрибута должно быть постоянным». Разве нулевая не константа ???

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SomeInterface {
    Class<? extends Foo> bar() default null;// this doesn't compile
}
потрошитель234
источник
Зачем кому-то это нужно (для компиляции или обходного пути)? Та же «функциональность» уже предоставляется 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

К сожалению, я продолжаю обнаруживать, что новые языковые функции (перечисления, а теперь и аннотации) содержат очень бесполезные сообщения об ошибках компилятора, когда вы не соответствуете спецификации языка.

РЕДАКТИРОВАТЬ: небольшой поиск в Google обнаружил следующее в JSR-308, где они выступают за разрешение нулей в этой ситуации:

Отметим некоторые возможные возражения против предложения.

Предложение не делает возможным то, что раньше было невозможно.

Специальное значение, определяемое программистом, обеспечивает лучшую документацию, чем null, что может означать «нет», «не инициализировано», само значение 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 {String value () default void.class; }
wjohnson
Для Groovy это работает нормально, даже в случаях, упомянутых в комментариях
Vampire
LOL: это ничего не значит против того, что означает «ничего»
Андреас
55

Казалось бы, это незаконно, хотя JLS очень расплывчато об этом.

Я напряг свою память, чтобы попытаться представить существующую аннотацию, которая имеет атрибут Class для аннотации, и вспомнил эту аннотацию из JAXB API:

@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
А в случае с полями String вам придется прибегнуть к магическим строкам . Очевидно, сейчас хорошо известные антипаттерны лучше, чем хорошо понятные языковые особенности.
Тим Йейтс
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.
Doradus
Вы использовали, .equals()а не ==@Doradus? Вы получили другое значение или другой объект.
Грей
Другой объект. Я использовал ==.
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 не равно нулю)», что звучит так, как будто любое ненулевое значение будет правильным. Формулировка JLS - настоящий зверь, но есть внешнее orи внутреннее and, о которых нельзя забывать.
maaartinus
@maaartinus Давно не видел вашего комментария, обновил свой ответ, чтобы добавить всю цитату.
Сотириос Делиманолис
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;
}

Основным недостатком этого (помимо необходимости перечислять возможные значения) является то, что теперь вы заключили свое значение в перечисление, поэтому вам нужно будет вызвать свойство / геттер перечисления, чтобы получить значение.

AMTerp
источник