2009-02-05 55 views
3

我有一個更復雜的問題(比問題'Java映射的值由鍵的類型參數限制'問題)映射鍵和值類型的映射。那就是:限制映射鍵和值類型 - 更復雜

interface AnnotatedFieldValidator<A extends Annotation> { 
    void validate(Field f, A annotation, Object target); 
    Class<A> getSupportedAnnotationClass(); 
} 

現在,我想存儲在地圖驗證,這樣我可以寫下面的方法:

validate(Object o) { 
    Field[] fields = getAllFields(o.getClass()); 
    for (Field field: fields) { 
    for (Annotation a: field.getAnnotations()) { 
     AnnotatedFieldValidator validator = validators.get(a); 
     if (validator != null) { 
     validator.validate(field, a, target); 
     } 
    } 
    } 
} 

(類型參數在這裏被省略,因爲我沒有解決方案)。我也希望能夠註冊我的驗證:

public void addValidator(AnnotatedFieldValidator<? extends Annotation> v) { 
    validators.put(v.getSupportedAnnotatedClass(), v); 
} 

有了這個頭(只)公共修改方法,我可以保證地圖包含其中的關鍵(註記類)驗證支持的註解類相匹配的條目。

這裏是一個嘗試:

我申報的驗證地圖是這樣的:

private Map<Class<? extends Annotation>, AnnotatedFieldValidator<? extends Annotation>> validators; 

我知道我不能正確鏈接鍵和值(鏈接假設OK由於僅通過訪問addValidator()),所以我嘗試了投:

for (Annotation a: field.getAnnotations()) { 
    AnnotatedFieldValidator<? extends Annotation> validator = validators.get(a); 
    if (validator != null) { 
    validator.validate(field, validator.getSupportedAnnotationClass().cast(a), target); 
    } 
} 

但是,這並不工作:The method validate(Field, capture#8-of ?, Object) in the type AnnotatedFieldValidator<capture#8-of ?> is not applicable for the arguments (Field, capture#9-of ?, Object)

我不明白爲什麼這不起作用:AnnotatedFieldValidator有一個單一的類型參數(A),它既用作getSupportedAnnotationClass()的返回類型,也用作參數validate();因此,在將註釋投射到supportedAnnotationClass時,我應該能夠將其作爲參數傳遞給validate()。爲什麼getSupportedAnnotationClass()的結果與validate()的參數被認爲是不同的類型?

我可以通過刪除驗證程序聲明和validate()方法中的通配符來解決validate()方法,但當然addValidator()不會編譯。

回答

0

謝謝大家的回答,它確實幫助我找到了以下解決方案。

flicken的答案告訴我方式:我必須將一些代碼提取到參數化方法中。但不是在方法中提取validators.get(),我可以提取整個驗證過程。這樣做時,我可以使用編程鑄造(我假定行自我控制鍵的將值映射中的相干性):

public void validate(Object o) { 
    Field[] fields = getFields(o.getClass()); 
    for (Field field : fields) { 
    Annotation[] annotations = field.getAnnotations(); 
    for (Annotation annotation : annotations) { 
     AnnotatedFieldValidator<? extends Annotation> validator = 
      validators.get(annotation.annotationType()); 
     if (validator != null) { 
     doValidate(field, validator, annotation, o); 
     } 
    } 
    } 
} 

然後,將doValidate()方法如下:

private <A extends Annotation> void doValidate(Field field, 
     AnnotatedFieldValidator<A> validator, Annotation a, Object o) { 
    // I assume this is correct following only access to validators Map 
    // through addValidator() 
    A annotation = validator.getSupportedAnnotationClass().cast(a); 
    try { 
     validator.validate(field, annotation, bean, beanName); 
    } catch (IllegalAccessException e) { 
    } 
} 

沒有強制轉換(好,除了Class.cast()...),沒有未經檢查的警告,沒有原始類型,我很高興。

1

您可以提取一個方法來獲取驗證器。所有對validators Map的訪問都是通過類型檢查的方法進行的,因此是類型安全的。

protected <A extends Annotation> AnnotatedFieldValidator<A> getValidator(A a) { 
     // unchecked cast, but isolated in method 
     return (AnnotatedFieldValidator<A>) validators.get(a); 
    } 

    public void validate(Object o) { 
     Object target = null; 
     Field[] fields = getAllFields(o.getClass()); 
     for (Field field : fields) { 
      for (Annotation a : field.getAnnotations()) { 
       AnnotatedFieldValidator<Annotation> validator = getValidator(a); 
       if (validator != null) { 
        validator.validate(field, a, target); 
       } 
      } 
     } 
    } 

    // Generic map 
    private Map<Class<? extends Annotation>, AnnotatedFieldValidator<? extends Annotation>> validators; 

(刪除第二個建議爲重複。)

0

泛型提供其中僅存在在編譯時間信息;在運行時所有的信息都會丟失。編譯泛型代碼時,編譯器會刪除所有泛型類型信息,並根據需要插入強制轉換。例如,

List<String> list = new ArrayList<String>(); 
list.add("test"); 
String s = list.get(0); 

將最終被編譯爲

List list = new ArrayList(); 
list.add("test"); 
String s = (String) list.get(0); 

與編譯器自動將在第三行鑄造。

它看起來像我正在嘗試使用泛型進行運行時類型安全。這不可能。在線路

validator.validate(field, a, target); 

有沒有辦法讓編譯器知道驗證所期待的亞型Annotation

我覺得做的最好的事情就是刪除該類型變量A,並宣佈你的界面如下:

interface AnnotatedFieldValidator { 
    void validate(Field f, Annotation annotation, Object target); 
    Class<? extends Annotation> getSupportedAnnotationClass(); 
} 

addValidator那麼也將失去它的參數化類型,即

public void addValidator(AnnotatedFieldValidator v) { 
    validators.put(v.getSupportedAnnotationClass(), v); 
} 

的不利的一面是,你將不得不檢查你的驗證器是否可以通過他們可以驗證的類的註釋。這在調用驗證器的代碼中最容易實現,例如

if (validator.getSupportedAnnotationClass().isInstance(a)) { 
    validator.validate(field, a, target); 
} 
else { 
    // wrong type of annotation, throw some exception. 
} 
+0

是的,編譯器無法知道。這就是爲什麼我有一個強制連貫性的方法(addValidator())。現在我假設這種連貫性,我應該可以調用Class.cast(Object)。您的解決方案仍然需要投射物體,這是我想避免的。 – Gaetan 2009-02-05 13:47:04

0

哈阿..我想我看着辦吧:)

for (Annotation a: field.getAnnotations()) { 
    AnnotatedFieldValidator<? extends Annotation> validator = validators.get(a); 
    if (validator != null) { 
    validator.validate(field, validator.getSupportedAnnotationClass().cast(a), target); 
    } 
} 

編輯:(改變了我的觀點)

當您從運行看它這種語義是正確的透視,但不在編譯時間

這裏的編譯器假設validator.getSupportedAnnotationClass().cast()會給你一個Class<? extends Annotation>

,當你撥打:

validator.validate(field, validator.getSupportedAnnotationClass().cast(a), target); 

編譯需要一個Class<? extends Annotation>作爲參數。

這是問題所在。從編譯器的角度來看,這些類型在運行時可能是2種不同的類型,雖然在這種情況下語義不允許這樣做。

+0

我不同意你:當我打電話給AnnotatedFieldValidator .getSupportedAnnotationClass()時,我得到了類。因此,這個類的cast()應該返回一個Deprecated的實例。 – Gaetan 2009-02-05 13:50:10

+0

請檢查我的更新回覆。 – 2009-02-05 18:44:08

0

好的,我會放棄這一點,因爲泛型是棘手的材料,我可能會從反應中學到一些東西。所以,請糾正我,如果我錯了

首先,讓我把你的源代碼放在一個塊中。 (改名爲AnnotatedFieldValidator到​​爲簡潔起見)

interface AFV<A extends Annotation> 
{ 
    void validate(Field f, A annotation, Object target); 
    Class<A> getSupportedAnnotationClass(); 
} 

private Map<Class<? extends Annotation>, AFV<? extends Annotation>> validators; 

public void validate(Object o) { 
    Field[] fields = o.getClass().getDeclaredFields(); 
    for (Field field: fields) { 
    for (Annotation a: field.getAnnotations()) { 
     AFV<? extends Annotation> validator = validators.get(a.getClass()); 
     if (validator != null) { 
     validator.validate(field, a, o); 
     } 
    } 
    } 
} 

public void addValidator(AFV<? extends Annotation> v) { 
    validators.put(v.getSupportedAnnotationClass(), v); 
} 

麻煩的是,當你遍歷一個字段的註釋,編譯器可以推斷出唯一類型是Annotation,而不是它的專長。如果您現在從Map中拉出正確的​​,並且您嘗試對其調用validate,則第二個參數會有一個類型衝突,因爲​​想要查看其參數化爲Annotation的特定子類,但僅得到Annotation,這太弱了。

正如你已經說自己,如果要添加驗證的唯一途徑就是通過addValidator方法,那麼你就可以在內部確保通過其類型安全,並改寫如下:

interface AFV<A extends Annotation> 
{ 
    void validate(Field f, A annotation, Object target); 
    Class<A> getSupportedAnnotationClass(); 
} 

private Map<Class<?>, AFV<?>> validators; 

public void validate(Object o) 
{ 
    Field[] fields = o.getClass().getDeclaredFields(); 
    for (Field field : fields) 
    { 
     for (Annotation a : field.getAnnotations()) 
     { 
      // raw type to keep compiler happy 
      AFV validator = validators.get(a.getClass()); 
      if (validator != null) 
      { 
       validator.validate(field, a, o); // statically unsafe 
      } 
     } 
    } 
} 

public void addValidator(AFV<?> v) 
{ 
    validators.put(v.getSupportedAnnotationClass(), v); 
} 

注意,通話validator.validate現在是靜態不安全的,編譯器會發出警告。但如果你能忍受這一點,這可能會做到。

+0

我必須同意我有點「極端主義」,但我更喜歡沒有原始類型的解決方案。 – Gaetan 2009-02-05 13:54:58