2009-02-05 79 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