Подтвердить что ты не робот

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

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

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

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

    T defaultValue();

}

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

BOUNTY

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

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

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

ЛУЧШЕЕ РЕШЕНИЕ СКОРОСТЬ

По Дюны:

@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 для полей для предоставленного значения по умолчанию.

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

4b9b3361

Ответ 1

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

Ответ 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. Однако вам придется получить доступ к значению с помощью отражения объекта аннотации. Похоже, что этот подход более сложный на стороне структуры.

Ответ 2

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

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

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();
    }
}

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

Ответ 3

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

Ответ 4

Синтаксис вашего родового типа немного не работает. Это должно быть:

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

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

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

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

Ответ 5

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

Для вашей проблемы вам нужно написать AnnotationProcessor (класс, используемый в качестве отправной точки для предварительной обработки), чтобы проанализировать аннотацию, используя Mirror 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, вы можете предоставить ему список на выбор, содержащий все константы, начинающиеся с B: BAR и BAZ.

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

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

Ответ 6

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

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

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

Вы просили:

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

Ответ 7

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

Фактический интерфейс @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:)