2011-08-15 199 views
18

Java允許enum作爲註釋值的值。如何爲enum註釋值定義一種通用默認enum值?Java枚舉註釋值的枚舉默認值

我已經考慮了以下,但它不會編譯:

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

    T defaultValue(); 

} 

是否有這個問題的解決與否?

BOUNTY

是似乎並不像有一個直接的解決方案,這個Java角落的情況。所以,我開始尋找解決這個問題的最優雅解決方案。

理想解決方案應該理想滿足以下條件:在所有枚舉

  1. 一個註解重複使用
  2. 最小的努力/複雜檢索默認枚舉值從註釋實例枚舉

SO OF FAR

沙丘:

@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上提供的默認值的字段。

這需要爲每個枚舉類型創建一個註釋類型,但是保證編譯時間檢查類型的安全性。

+0

難道這不是毫無意義嗎?每當你在運行時處理註解時,通用信息就會丟失。 – Dunes

+0

這將避免我必須爲每個枚舉類型定義一個註釋,以便在代碼中使用(這是編譯時問題)。 – JVerstry

+0

我不是故意沒有用。但是泛型只在編譯時才知道。您已將註釋標記爲保留在運行時中。但要訪問註釋,你必須通過思考 - 你不會有任何想法是什麼泛型類型。 – Dunes

回答

2

我不知道你的使用情況是什麼檢索默認枚舉值,所以我有兩個答案:

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

clazzImplicitType.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; 

在這種情況下,你會掃描領域再次被註解爲的註解。不過,您必須通過註釋對象上的反射來訪問該值。似乎這種方法在框架方面更爲複雜。

+0

看起來很有趣,但是不應該在您的MyAnnotation版本中保留clazz,並且不應該使用@MyAnnotation(clazz = MyEnumType.class)對MyEnumAnnotation進行註釋?或者我錯過了什麼? – JVerstry

+0

我希望現在更清楚。 – Cephalopod

+0

對於第二種方法,您不需要clazz字段,因爲您可以直接從客戶端註釋中獲取枚舉值。如果您還需要值類型,則可以使用value()函數的返回類型。 – Cephalopod

3

簡而言之,你不能那樣做。枚舉不能輕易用作泛型類型;也許有一個例外,那就是枚舉實際上可以實現允許動態使用的接口。但是這不適用於註釋,因爲可以使用的類型集合受到嚴格限制。

3

您的通用類型語法有點關閉。它應該是:

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

但是編譯器會發出錯誤:

Syntax error, annotation declaration cannot have type parameters

好主意。看起來不受支持。

+0

是的,我也試過。但... – JVerstry

5

如果在構造函數參數中沒有提供所述值,但在運行時不關心通用類型,則不完全確定您說的意思是獲取默認值。

下面的工作,但是有一點醜陋的黑客。

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方法一樣工作,從而得到更好的錯誤消息,如果給定的值不引用枚舉的實例。

+0

Oooh,在這裏探索的好主意...... – JVerstry

+0

「如果在構造函數args中未提供所述值時獲取默認值,則不完全確定您的意思」 - >應該定義默認值在註釋項目上的註釋「實例」中。 – JVerstry

3

使用註釋的框架可以真正從使用apt中獲益。這是一個預處理程序,包含在javac中,它可讓您分析聲明及其註釋(但不包括方法內的本地聲明)。

對於您的問題,您需要編寫AnnotationProcessor(用作預處理起點的類)以使用Mirror API分析註釋。實際上沙丘的註釋非常接近這裏所需要的。太糟糕的枚舉名稱不是常量表達式,否則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。

我建議實現默認值是創建一個標記註釋來聲明一個枚舉常量爲默認該枚舉的值。用戶仍然需要提供枚舉類型,但可以省略名稱。可能還有其他方法,可以將值設爲默認值。

這裏是關於apt的tutorial,這裏的AbstractProcessor應該擴展以覆蓋getCompletions方法。

+0

謝謝。我知道關於註釋處理器。我看到了Dune的解決方案。我只是想知道是否有人有更好的主意。這就是我設定賞金的原因。 – JVerstry

+1

使用處理器檢查註釋比總是在運行時檢查註釋更好。你建議的泛型會很好,但不幸的是不可用。枚舉名稱似乎是解決問題的唯一方法(除非使用其他名稱而不是枚舉),但它們不是編譯時間常量,因此必須寫爲編譯時常量字符串。在編譯時驗證這些字符串對用戶來說非常方便。 – Kapep

2

我的建議類似於kapep's的建議。區別在於我建議使用註釋處理器來創建代碼。

一個簡單的例子是,如果你打算只用於你自己寫的枚舉。用一個特殊的枚舉對枚舉進行註釋。註釋處理器然後將爲該枚舉生成新的註釋。

如果你使用了很多沒有編寫的枚舉,那麼你可以實現一些名稱映射方案:enum name - > annotation name。然後,當註釋處理程序在您的代碼中遇到這些枚舉之一時,它會自動生成適當的註釋。

您問:在所有枚舉

  1. 一個註解重複使用......在技術上沒有,但我認爲效果是一樣的。
  2. 最小的努力/複雜檢索默認枚舉值從註釋實例枚舉...你可以不需要任何特殊的處理
0

我也有類似的需求,並具有以下非常簡單的解決方案提出了:

實際@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 :)