2011-08-15 6 views
18

Java dopuszcza wartości enum jako wartości adnotacji. Jak zdefiniować rodzaj ogólnej domyślnej wartości enum dla wartości adnotacji enum?Wartość domyślna wyliczenia dla wartości adnotacji wyliczeniowej Java

Rozważałam dodaje, ale nie będzie skompilować:

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

    T defaultValue(); 

} 

Czy istnieje rozwiązanie tego problemu, czy nie?

BOUNTY

nie wydaje się to jak istnieje bezpośredni rozwiązanie tego przypadku narożnego Java. Tak więc zaczynam nagrodę, aby znaleźć najbardziej eleganckie rozwiązanie tego problemu.

Rozwiązanie idealny powinny idealnie spełniać następujące kryteria:

  1. Jeden adnotacja wielokrotnego użytku na wszystkich teksty stałe
  2. Minimalny wysiłek/złożoność pobrać wartość domyślną enum jako enum od przypadkach adnotacji

NAJLEPSZE ROZWIĄZANIE TAK DUŻE

wydm:

@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; 

Oczywiście, nie możemy zmusić użytkownika, aby określić prawidłową wartość domyślną w czasie kompilacji. Jednak każde wstępne przetwarzanie adnotacji może to sprawdzić za pomocą valueOf().

POPRAWA

Arian zapewnia eleganckie rozwiązanie, aby pozbyć się clazz w adnotacjami dziedzinach:

@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; 

Procesor adnotacja powinna szukać zarówno MyEnumAnnotation na polach podanej wartości domyślnej.

Wymaga to utworzenia jednego typu adnotacji na typ wyliczeniowy, ale gwarantuje kompilację typu sprawdzanie czasu bezpieczeństwa.

+0

Czy nie byłoby to bezsensowne? Kiedykolwiek przejdziesz do przetwarzania adnotacji w czasie wykonywania, informacje ogólne zostaną utracone. – Dunes

+0

Unikałoby to konieczności definiowania jednej adnotacji na typ enum do użycia w moim kodzie (jest to problem czasu kompilacji). – JVerstry

+0

Nie miałem na myśli, że pomysł nie jest przydatny. Ale generyczne są znane tylko podczas kompilacji. Masz zaznaczone adnotacje, które mają zostać zachowane w środowisku wykonawczym. Ale aby uzyskać dostęp do adnotacji, musisz przejść przez refleksję - nie miałbyś pojęcia, jaki pierwotnie był typ generyczny. – Dunes

Odpowiedz

2

Nie jestem pewien, co Twój przypadek użycia jest więc mam dwie odpowiedzi:

odpowiedź 1:

Jeśli chcesz po prostu napisać kod tak mało, jak to możliwe, tutaj jest moja propozycja powiększenia wydm odpowiedź:

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; 

Gdy clazz jest ImplicitType.class, należy użyć typu pól jako klasy wyliczeniowej.

Odpowiedź 2:

Jeśli chcesz zrobić kilka ramową magii i chcą utrzymać bezpieczeństwo kompilator sprawdzane typu, można zrobić coś takiego:

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

iw kodzie klienta, W takim przypadku można przeskanować pole w poszukiwaniu adnotacji, które ponownie opatrzone są komentarzem.. Będziesz jednak musiał uzyskać dostęp do wartości poprzez odbicie w obiekcie adnotacji. Wygląda na to, że takie podejście jest bardziej złożone po stronie struktury.

+0

Wygląda bardzo interesująco, ale nie powinno pozostać w wersji MyAnnotation i nie powinno być adnotowane w MyEnumAnnotation przez @MyAnnotation (clazz = MyEnumType.class)? Czy może czegoś brakuje? – JVerstry

+0

Mam nadzieję, że teraz będzie bardziej przejrzyste. – Cephalopod

+0

Dla drugiego podejścia nie potrzebujesz pola clazz, ponieważ możesz uzyskać wartość wyliczenia bezpośrednio z adnotacji klienta. Jeśli potrzebujesz również typu wartości, możesz użyć typu zwracanego funkcji value(). – Cephalopod

3

Po prostu nie można tego zrobić. Wyników nie można łatwo używać jako typów ogólnych; z być może jednym wyjątkiem, co oznacza, że ​​Enums może faktycznie implementować interfejsy, które pozwalają na nieco dynamiczne użycie. Ale to nie zadziała z adnotacjami, ponieważ zestaw typów, które można wykorzystać, jest ściśle ograniczony.

3

Twoja ogólna składnia jest trochę wyłączona. Powinno być:

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

ale kompilator daje błąd:

Syntax error, annotation declaration cannot have type parameters

niezły pomysł. Wygląda na to, że nie jest obsługiwany.

+0

Tak, też tego próbowałem. Ale ... – JVerstry

5

Nie do końca pewien co masz na myśli mówiąc uzyskać wartość domyślną, jeśli ta wartość nie była przewidziana w args konstruktora, ale nie należy dbanie o typu rodzajowego w czasie wykonywania.

Poniższe działa, ale jest trochę brzydki hack, choć.

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: Zmieniłem getDefaultValue pracować metodą valueOf o teksty stałe, dając lepszą komunikat o błędzie, jeśli wartość nie jest podana odwoływać instancji wyliczenia.

+0

Oooh, fajny pomysł do odkrycia tutaj ... – JVerstry

+0

"Nie jestem całkowicie pewien, co masz na myśli, kiedy mówisz Uzyskaj wartość domyślną, jeśli wspomniana wartość nie została podana w argumencie konstruktora" -> Wartość domyślna powinna być zdefiniowana w adnotacji "instancja" nad adnotacją. – JVerstry

3

Konstrukcje wykorzystujące adnotacje mogą naprawdę korzystać z użyciem apt. Jest to preprocesor zawarty w javac, który pozwoli ci analizować deklaracje i ich adnotacje (ale nie lokalne deklaracje wewnątrz metod).

Aby rozwiązać problem, należy napisać AnnotationProcessor (klasę używaną jako punkt początkowy do wstępnego przetwarzania) w celu przeanalizowania adnotacji za pomocą Mirror API. W rzeczywistości adnotacja Dunes jest bardzo zbliżona do tego, co jest tutaj potrzebne. Zbyt złe nazwy enumowe nie są wyrażeniami stałymi, w przeciwnym razie rozwiązanie Dunes byłoby całkiem miłe.

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

A oto przykład enum: enum MyEnum { FOO, BAR, BAZ, ; }

Przy zastosowaniu nowoczesnych IDE, można wyświetlić błędy dotyczące bezpośrednio na elemencie adnotacji (lub wartości adnotacją za), jeśli nazwa nie jest poprawnym enum stały. Możesz nawet podać automatycznie uzupełnione wskazówki, więc gdy użytkownik napisze @MyAnnotation(clazz = MyEnum.class, name = "B") i uderza klawiszami szybkiego dostępu do automatycznego uzupełniania po wpisaniu B, możesz podać mu listę do wyboru, zawierającą wszystkie stałe zaczynające się od B: BAR i BAZ.

Moja sugestia, aby wprowadzić wartość domyślną, polega na utworzeniu adnotacji znacznika w celu zadeklarowania stałej wyliczeniowej jako wartości domyślnej dla tego wyliczenia. Użytkownik nadal musiałby podać typ wyliczeniowy, ale mógł pominąć nazwę. Są prawdopodobnie inne sposoby, aby ustawić wartość domyślną.

Oto tutorial o apt i tutaj AbstractProcessor, który należy rozszerzyć, aby zastąpić metodę getCompletions.

+0

Dzięki. Wiem o procesorze adnotacji. Widziałem rozwiązanie Dune. Zastanawiam się tylko, czy ktoś ma jeszcze lepszy pomysł. Właśnie dlatego ustanowiłem nagrodę. – JVerstry

+1

Sprawdzanie adnotacji raz z procesorem jest lepsze niż zawsze sprawdzanie ich w czasie wykonywania. Proponowane leki generyczne byłyby dobre, ale niestety nie są dostępne. Nazwy enum wydają się być jedynym sposobem na rozwiązanie problemu (chyba że użyjesz czegoś innego zamiast wyliczenia), ale nie są one stałą czasu kompilacji i dlatego muszą być napisane jako stałe łańcuchy czasu kompilacji. Sprawdzanie poprawności tych łańcuchów w czasie kompilacji byłoby całkiem wygodne dla użytkownika. – Kapep

2

Moja propozycja jest podobna do kapep's sugestii. Różnica polega na tym, że proponuję użyć procesora adnotacji do tworzenia kodu.

Prostym przykładem może być, jeśli przeznaczone są do wykorzystania tylko dla tego teksty stałe, że sam napisał. Opisz enum w specjalnym wyliczeniu. Procesor adnotacji wygeneruje nową adnotację tylko dla tego wyliczenia.

Jeśli pracujesz z wieloma teksty stałe, które nie piszą, a następnie można wdrożyć jakiś schemat mapowania nazwa: enum nazwa -> nazwa adnotacji. Następnie, gdy procesor adnotacji napotka jeden z tych wyliczeń w kodzie, wygeneruje odpowiednią adnotację automatycznie.

Pytałeś o:

  1. One adnotacji wielokrotnego użytku na wszystkich teksty stałe ... technicznie nie, ale myślę, że efekt jest taki sam.
  2. Minimalny wysiłek/złożoność pobrać wartość domyślną enum jako enum od przypadkach adnotacji ... można pobrać wartość domyślną enum bez specjalnego przetworzenia
0

miałem podobną potrzebę i wpadł na poniższym dość proste rozwiązanie:

Faktyczny interfejs @Default:

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

Zastosowanie:

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

Znalezienie domyślne:

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

Odtwarzałem także z powracaniem Optional<T> zamiast domyślnej do pierwszej stałej Enum zadeklarowanej w klasie.

To oczywiście będzie domyślna deklaracja dla całej klasy, ale pasuje do tego, czego potrzebuję. YMMV :)