Значение Enum по умолчанию для значения аннотации перечисления Java

Java позволяет использовать enum в качестве значений для значений аннотаций. Как я могу определить тип общего значения по умолчанию enum для значения аннотации enum?

Я рассмотрел следующее, но он не будет компилироваться:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public <T extends Enum<T>> @interface MyAnnotation<T> {

    T defaultValue();

}

Есть решение этой проблемы или нет?

ПОДАРОК

Не похоже, что есть прямое решение для этого углового случая Java. Итак, я запускаю щедрость, чтобы найти самое элегантное решение этой проблемы.

идеальное решение должно идеально соответствовать следующим критериям:

  1. Одна аннотация, которую можно использовать повторно во всех перечислениях
  2. Минимальные усилия/сложность для получения значения перечисления по умолчанию в виде перечисления из экземпляров аннотаций.

САМОЕ ЛУЧШЕЕ РЕШЕНИЕ

По дюнам:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyAnnotation {

    // By not specifying default,
    // we force the user to specify values
    Class<? extends Enum<?>> enumClazz();
    String defaultValue();

}

...

public enum MyEnumType {
    A, B, D, Q;
}

...

// Usage
@MyAnnotation(enumClazz=MyEnumType.class, defaultValue="A"); 
private MyEnumType myEnumField;

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

УЛУЧШЕНИЕ

Arian предлагает элегантное решение, позволяющее избавиться от clazz в аннотированных полях:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyAnnotation {

}

...

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@MyAnnotation()
public @interface MyEnumAnnotation {

    MyEnumType value(); // no default has user define default value

}

...

@MyEnumAnnotation(MyEnum.FOO)
private MyEnumType myValue;

Обработчик аннотаций должен искать оба поля MyEnumAnnotation для предоставленного значения по умолчанию.

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


person Jérôme Verstrynge    schedule 15.08.2011    source источник
comment
Не будет ли это бессмысленным? Всякий раз, когда вы приступаете к обработке аннотации во время выполнения, общая информация будет потеряна.   -  person Dunes    schedule 16.08.2011
comment
Это избавило бы меня от необходимости определять одну аннотацию для каждого типа перечисления для использования в моем коде (это проблема времени компиляции).   -  person Jérôme Verstrynge    schedule 16.08.2011
comment
Я не имел в виду, что идея бесполезна. Но дженерики известны только во время компиляции. Вы пометили свою аннотацию для сохранения во время выполнения. Но чтобы получить доступ к аннотации, вам нужно пройти через отражения — вы не будете иметь ни малейшего представления о том, каким изначально был универсальный тип.   -  person Dunes    schedule 16.08.2011
comment
Хорошо, но мне не интересно знать общий тип во время выполнения. Я просто хочу убедиться, что у меня есть значение по умолчанию, когда я инициирую значение поля перечисления объекта, если оно не указано в конструкторе (например).   -  person Jérôme Verstrynge    schedule 16.08.2011
comment
Каков ваш вариант использования для объявления значений по умолчанию с использованием аннотаций, которые вы не можете получить с помощью простого private MyEnum field = MyEnum.DEFAULT;?   -  person Alistair A. Israel    schedule 16.08.2011
comment
@AlistairIsrael Ну, могут потребоваться разные значения по умолчанию для различного использования аннотации в разных полях. С вашим решением это невозможно.   -  person Jérôme Verstrynge    schedule 16.08.2011
comment
Извините, я все еще не понимаю. Я бы просто использовал private MyEnum field1 = MyEnum.VALUE1;, а затем private MyEnum field2 = MyEnum.VALUE2;. Я до сих пор не понимаю, что аннотация приносит на стол.   -  person Alistair A. Israel    schedule 16.08.2011
comment
Аннотации могут быть предварительно обработаны для создания дополнительных классов, содержащих шаблонный код, во время компиляции. Если я поставлю значение по умолчанию в аннотацию, у меня будет доступ к нему во время предварительной обработки. Если я помещу его в свой класс, доступ к нему все еще возможен, но это намного сложнее. Это совсем не элегантно при создании/использовании фреймворка.   -  person Jérôme Verstrynge    schedule 16.08.2011
comment
Я подозревал, что вы пытаетесь создать своего рода структуру. Итак, вы хотите иметь возможность сканировать/обрабатывать аннотации во время компиляции, подобно XDoclet? Теперь я вижу, откуда ты.   -  person Alistair A. Israel    schedule 16.08.2011
comment
@JVerstry позвольте нам продолжить это обсуждение в чате   -  person Alistair A. Israel    schedule 16.08.2011


Ответы (7)


Я не уверен, каков ваш вариант использования, поэтому у меня есть два ответа:

Ответ 1:

Если вы просто хотите написать как можно меньше кода, вот мое предложение, расширяющее ответ Dunes:

public enum ImplicitType {
    DO_NOT_USE;
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyAnnotation {

    Class<? extends Enum<?>> clazz() default ImplicitType.class;

    String value();
}

@MyAnnotation("A"); 
private MyEnumType myEnumField;

Когда clazz равно ImplicitType.class, используйте тип полей как класс перечисления.

Ответ 2:

Если вы хотите использовать некоторую магию фреймворка и хотите поддерживать безопасность типов, проверенных компилятором, вы можете сделать что-то вроде этого:

/** Marks annotation types that provide MyRelevantData */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface MyAnnotation {
}

И в клиентском коде у вас будет

/** Provides MyRelevantData for TheFramework */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@MyAnnotation
public @interface MyEnumAnnotation {

    MyEnumType value(); // default MyEnumType.FOO;

}

@MyEnumAnnotation(MyEnum.FOO)
private MyEnumType myValue;

В этом случае вы должны просмотреть поле на наличие аннотаций, которые снова отмечены MyAnnotation. Однако вам нужно будет получить доступ к значению через отражение объекта аннотации. Похоже, этот подход более сложен на стороне фреймворка.

person Cephalopod    schedule 23.08.2011
comment
Выглядит очень интересно, но разве clazz не должен оставаться в вашей версии MyAnnotation и не должен ли MyEnumAnnotation быть аннотирован @MyAnnotation(clazz=MyEnumType.class)? Или я что-то упускаю? - person Jérôme Verstrynge; 23.08.2011
comment
Для второго подхода вам не нужно поле clazz, так как вы можете получить значение перечисления непосредственно из клиентской аннотации. Если вам также нужен тип значения, вы можете использовать возвращаемый тип функции value(). - person Cephalopod; 24.08.2011

Не совсем уверен, что вы имеете в виду, когда говорите, что получите значение по умолчанию, если указанное значение не было указано в аргументах конструктора, но не заботитесь об универсальном типе во время выполнения.

Следующее работает, но это немного уродливый хак.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class Main {

    @MyAnnotation(clazz = MyEnum.class, name = "A")
    private MyEnum value;

    public static v oid main(String[] args) {
        new Main().printValue();
    }

    public void printValue() {
        System.out.println(getValue());
    }

    public MyEnum getValue() {
        if (value == null) {
            value = getDefaultValue("value", MyEnum.class);
        }
        return value;
    }

    private <T extends Enum<?>> T getDefaultValue(String name, Class<T> clazz) {

        try {
            MyAnnotation annotation = Main.class.getDeclaredField(name)
                    .getAnnotation(MyAnnotation.class);

            Method valueOf = clazz.getMethod("valueOf", String.class);

            return clazz.cast(valueOf.invoke(this, annotation.value()));

        } catch (SecurityException e) {
            throw new IllegalStateException(e);
        } catch (NoSuchFieldException e) {
            throw new IllegalArgumentException(name, e);
        } catch (IllegalAccessException e) {
            throw new IllegalStateException(e);
        } catch (NoSuchMethodException e) {
                throw new IllegalStateException(e);
        } catch (InvocationTargetException e) {
            if (e.getCause() instanceof RuntimeException) {
                throw (RuntimeException) e.getCause();
                /* rethrow original runtime exception 
                 * For instance, if value = "C" */
            }
            throw new IllegalStateException(e);
        }
    }

    public enum MyEnum {
        A, B;
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface MyAnnotation {

        Class<? extends Enum<?>> clazz();

        String name();
    }
}

изменить: я изменил getDefaultValue для работы с помощью метода перечисления valueOf, что дает лучшее сообщение об ошибке, если заданное значение не является эталонным экземпляром перечисления.

person Dunes    schedule 15.08.2011
comment
Не совсем уверен, что вы имеете в виду, когда говорите, что получите значение по умолчанию, если указанное значение не было предоставлено в аргументах конструктора -> Значение по умолчанию должно быть определено в «экземпляре» аннотации над аннотированным элементом. - person Jérôme Verstrynge; 16.08.2011

Проще говоря, вы не можете этого сделать. Перечисления нельзя легко использовать в качестве универсальных типов; возможно, с одним исключением, которое заключается в том, что Enums может фактически реализовывать интерфейсы, которые допускают несколько динамическое использование. Но это не будет работать с аннотациями, поскольку набор типов, которые можно использовать, строго ограничен.

person StaxMan    schedule 15.08.2011

Синтаксис вашего универсального типа немного отличается. Должен быть:

public @interface MyAnnotation<T extends Enum<T>> {...

но компилятор выдает ошибку:

Синтаксическая ошибка, объявление аннотации не может иметь параметры типа

Хорошая идея. Похоже не поддерживается.

person Bohemian♦    schedule 15.08.2011

Фреймворки, использующие аннотации, могут действительно выиграть от использования apt. . Это препроцессор, содержащийся в javac, который позволит вам анализировать объявления и их аннотации (но не локальные объявления внутри методов).

Для вашей проблемы вам нужно будет написать AnnotationProcessor (класс, используемый в качестве отправной точки для предварительной обработки) для анализа аннотации с помощью Зеркальный API. На самом деле аннотация Dunes довольно близка к тому, что здесь нужно. Жаль, что имена перечислений не являются постоянными выражениями, иначе решение Dunes было бы довольно хорошим.

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface MyAnnotation {
    Class<? extends Enum<?>> clazz();
    String name() default "";
}

А вот пример перечисления: enum MyEnum { FOO, BAR, BAZ, ; }

При использовании современной IDE вы можете отображать ошибки непосредственно в элементе аннотации (или значении аннотации), если имя не является допустимой константой перечисления. Вы даже можете предоставить подсказки по автозаполнению, поэтому, когда пользователь напишет @MyAnnotation(clazz = MyEnum.class, name = "B") и нажмет горячие клавиши для автозавершения после написания B, вы можете предоставить ему список на выбор, содержащий все константы, начинающиеся с < em>B: БАР и БАЗ.

Мое предложение реализовать значение по умолчанию состоит в том, чтобы создать аннотацию маркера, чтобы объявить константу перечисления как значение по умолчанию для этого перечисления. Пользователю по-прежнему необходимо указать тип перечисления, но он может опустить имя. Однако, вероятно, есть и другие способы сделать значение значением по умолчанию.

Вот руководство по apt и здесь AbstractProcessor, который следует расширить, чтобы переопределить метод getCompletions.

person kapex    schedule 18.08.2011
comment
Спасибо. Я знаю о процессоре аннотаций. Я видел решение Дюны. Мне просто интересно, есть ли у кого-то еще лучшая идея. Вот почему я установил награду. - person Jérôme Verstrynge; 18.08.2011
comment
Однократная проверка аннотаций процессором лучше, чем их постоянная проверка во время выполнения. Предложенные вами дженерики были бы хороши, но, к сожалению, недоступны. Имена перечислений кажутся единственным способом решить проблему (если вы не используете что-то другое вместо перечислений), но они не являются константами времени компиляции и поэтому должны быть записаны как строки констант времени компиляции. Проверка этих строк во время компиляции была бы весьма удобна для пользователя. - person kapex; 18.08.2011

Мое предложение похоже на предложение kapep. Разница в том, что я предлагаю использовать обработчик аннотаций для создания кода.

Простым примером может быть, если вы намеревались использовать это только для перечислений, которые вы сами написали. Аннотируйте перечисление специальным перечислением. Затем процессор аннотаций сгенерирует новую аннотацию только для этого перечисления.

Если вы работаете с большим количеством перечислений, которые вы не писали, то вы могли бы реализовать некоторую схему сопоставления имен: имя перечисления -> имя аннотации. Затем, когда обработчик аннотаций встречал одно из этих перечислений в вашем коде, он автоматически генерировал соответствующую аннотацию.

Вы просили:

  1. Одна аннотация, которую можно использовать повторно для всех перечислений... технически нет, но я думаю, что эффект тот же.
  2. Минимальные усилия/сложность для получения значения перечисления по умолчанию в виде перечисления из экземпляров аннотации... вы можете получить значение перечисления по умолчанию без какой-либо специальной обработки
person emory    schedule 20.08.2011

У меня была аналогичная потребность, и я придумал следующее довольно простое решение:

Фактический интерфейс @Default:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Default {}

Использование:

public enum Foo {
    A,
    @Default B,
    C;
}

Находим значение по умолчанию:

public abstract class EnumHelpers {
    public static <T extends Enum<?>> T defaultEnum(Class<T> clazz) {
        Map<String, T> byName = Arrays.asList(clazz.getEnumConstants()).stream()
            .collect(Collectors.toMap(ec -> ec.name(), ec -> ec));

        return Arrays.asList(clazz.getFields()).stream()
             .filter(f -> f.getAnnotation(Default.class) != null)
             .map(f -> byName.get(f.getName()))
             .findFirst()
             .orElse(clazz.getEnumConstants()[0]);
    }   
}

Я также играл с возвратом Optional<T> вместо того, чтобы по умолчанию использовать первую константу Enum, объявленную в классе.

Это, конечно, будет объявление по умолчанию для всего класса, но это соответствует тому, что мне нужно. YMMV :)

person Michael Ressler    schedule 27.03.2017