ограничить ключ карты и типы значений - более сложный - PullRequest
3 голосов
/ 05 февраля 2009

У меня есть более сложная проблема (чем вопрос «Карта 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;

Я знаю, что не могу должным образом связать ключ и значение (связь считается нормальной из-за доступа только через 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(); таким образом, при приведении аннотации к supportAnnotationClass я должен иметь возможность передать его в качестве параметра validate(). Почему результат getSupportedAnnotationClass() считается типом, отличным от параметра validate()?

Я могу решить метод validate(), удалив символы подстановки в объявлении валидаторов и метод validate(), но тогда, конечно, addValidator() не скомпилируется.

Ответы [ 5 ]

1 голос
/ 05 февраля 2009

Вы можете извлечь метод, чтобы получить валидатор. Весь доступ к карте validators осуществляется с помощью метода проверки типа и, следовательно, является безопасным для типа.

    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 голосов
/ 05 февраля 2009

Спасибо всем за ваши ответы, это действительно помогло мне прийти к следующему решению.

Ответ от 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 () ...), без непроверенных предупреждений, без необработанного типа, я счастлив.

0 голосов
/ 05 февраля 2009

Хорошо, я попробую, поскольку дженерики - сложный материал, и я могу кое-что узнать из реакций. Итак, пожалуйста, поправьте меня, если я ошибаюсь .

Во-первых, позвольте мне поместить ваш исходный код в один блок. (переименован 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 теперь статически небезопасен, и компилятор выдаст предупреждение. Но если вы можете жить с этим, это может сделать.

0 голосов
/ 05 февраля 2009

Хаа .. Думаю, я это выясню :) 1001 *

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> в качестве аргумента.

Вот проблема. С точки зрения компилятора, эти типы могут быть 2 разных типов во время выполнения, хотя в этом случае семантика не позволяет этого.

0 голосов
/ 05 февраля 2009

Обобщения предоставляют информацию, которая существует только во время компиляции; во время выполнения вся информация теряется. При компиляции кода с обобщениями компилятор удаляет всю информацию об обобщенных типах и при необходимости вставляет приведение типов. Например,

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.
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...