限制地图键和值类型 - 更复杂

时间:2009-02-05 11:52:02

标签: java generics

我有一个更复杂的问题(问题是'Java map,其值受键的类型参数'问题限制),用于映射Map中的键和值类型。这是:

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

使用这个(仅)公共修饰符方法,我可以确保映射包含键(注释类)与验证器支持的注释类匹配的条目。

这是一个尝试:

我声明验证器Map如下:

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

我知道我无法正确链接密钥和值(由于只能通过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()的返回类型,又用作{的参数{1}};因此,当将注释转换为supportedAnnotationClass时,我应该能够将它作为参数传递给validate()。为什么validate()的结果被认为是与getSupportedAnnotationClass()的参数不同的类型?

我可以通过在验证器声明和validate()方法中删除通配符来解决validate()方法,但当然,validate()无法编译。

5 个答案:

答案 0 :(得分: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;

(删除第二个建议重复。)

答案 1 :(得分: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.
}

答案 2 :(得分:0)

哈...我想我明白了:)

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

编辑:(改变了我的观点)

从运行时的角度来看这个语义是正确的,而不是 COMPILE TIME

此处编译器假定validator.getSupportedAnnotationClass().cast()会为您提供Class<? extends Annotation>

当你打电话时:

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

编译器期望Class<? extends Annotation>作为参数。

这是问题所在。从编译器的角度来看,这些类型在运行时可能是两种不同的类型,尽管在这种情况下语义不允许这样做。

答案 3 :(得分:0)

好的,我会放手一搏,因为泛型是棘手的材料,我可以从反应中学到一些东西。所以,如果我错了,请纠正我

首先,让我把你的源代码放在一个块中。 (为了简明起见,将AnnotatedFieldValidator重命名为AFV

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,而不是它的特化。如果您现在从AFV中提取正确的Map,并尝试在其上调用validate,则第二个参数会出现类型冲突,因为AFV想要查看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现在静态不安全,编译器将发出警告。但如果你可以忍受,那可能会这样做。

答案 4 :(得分: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()...),没有未经检查的警告,没有原始类型,我很高兴。