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

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

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

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

    T defaultValue();

}

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

награда

Is не похоже, что есть прямое решение для этого углового случая 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().

улучшение

Ариан предлагает элегантное решение, чтобы избавиться от 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 on поля для предоставленного значения по умолчанию.

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

7 ответов


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

ответ 1:

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

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 is ImplicitType.class используйте тип поля класса enum.

ответ 2:

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

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


Не совсем уверен, что вы имеете в виду, когда говорите получить значение по умолчанию, если указанное значение не было предоставлено в конструкторе 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 перечислений, тем самым давая лучшее сообщение об ошибке, если заданное значение не является ссылочным экземпляром перечисления.


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


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

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

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

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

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


рамок с помощью аннотаций можно очень выгодно использовать apt. Это preprocesor, содержащиеся в 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, вы может предоставить ему список на выбор, содержащий все константы, начинающиеся с B: бар и баз.

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

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


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

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

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

вы спросили:

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

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

фактический @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> вместо дефолта к первой константе перечисления, объявленной в классе.

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