Общие и класс <? extends Enum <? >>, EnumSet.allOf(класс) vs class.getEnumConstants()

У меня есть следующий код BeanValidation, который отлично работает и позволяет проверить, что a bean аннотируется с помощью

  @EnumValue(enumClass = MyTestEnum.class)
  private String field;

  public enum MyTestEnum {
    VAL1, VAL2;
  }

Будет проверяться, только если значение поля "VAL1" или "VAL2".

public class EnumNameValidator implements ConstraintValidator<EnumValue, String> {

  private Set<String> AVAILABLE_ENUM_NAMES;

  @Override
  public void initialize(EnumValue enumValue) {
    Class<? extends Enum<?>> enumSelected = enumValue.enumClass();
    Set<? extends Enum<?>> enumInstances = Sets.newHashSet(enumSelected.getEnumConstants());
    AVAILABLE_ENUM_NAMES = FluentIterable
            .from(enumInstances)
            .transform(PrimitiveGuavaFunctions.ENUM_TO_NAME)
            .toImmutableSet();
  }

  @Override
  public boolean isValid(String value, ConstraintValidatorContext context) {
    if ( value == null ) {
      return true;
    } else {
      return AVAILABLE_ENUM_NAMES.contains(value);
    }
  }

}

Я не понимаю, почему моя первая попытка не удалась. Используя вместо enumSelected.getEnumConstants() выше следующий код:

Set<? extends Enum<?>> enumInstances = EnumSet.allOf(enumSelected);

Intellij 12 не выделяет никаких ошибок, но компилятор говорит:

java: method allOf in class java.util.EnumSet<E> cannot be applied to given types;
  required: java.lang.Class<E>
  found: java.lang.Class<capture#1 of ? extends java.lang.Enum<?>>
  reason: inferred type does not conform to declared bound(s)
    inferred: capture#1 of ? extends java.lang.Enum<?>
    bound(s): java.lang.Enum<capture#1 of ? extends java.lang.Enum<?>>

Я не понимаю проблему, и у меня также есть код, который отлично работает:

  private static <T extends Enum<T> & EnumAlternativeName> T safeGetByAlternativeName(Class<T> enumClass, String alternativeName) {
    for ( T t : EnumSet.allOf(enumClass) ) {
      if ( t.getAlternativeName().equals(alternativeName) ) {
        return t;
      }
    }
    return null;
  }

Ответ 1

Я предполагаю, что в ? extends Enum<?> два ? могут быть разными, тогда как allOf ожидает a T extends Enum<T>, где оба T совпадают.

Например, рассмотрим следующий код:

static enum MyEnum {}
static class EnumValue<T extends Enum<T>> {
    Class<T> enumClass;
    EnumValue(Class<T> enumClass) {
        this.enumClass = enumClass;
    }
    Class<T> enumClass() { return enumClass; }
}

Эти строки будут скомпилированы:

EnumValue<?> enumValue = new EnumValue(MyEnum.class); // raw constructor
Set<? extends Enum<?>> enumInstances = EnumSet.allOf(enumValue.enumClass());

потому что мы знаем, что два T в enumValue.enumClass() совпадают, но это не будет:

EnumValue enumValue = new EnumValue(MyEnum.class);
Class<? extends Enum<?>> enumSelected = enumValue.enumClass();
Set<? extends Enum<?>> enumInstances = EnumSet.allOf(enumSelected);

поскольку вы потеряли информацию, используя Class<? extends Enum<?>> в качестве промежуточного шага.

Ответ 2

Мои объяснения по решению @assylias:

Что мы хотим выразить о типе класса, так это то, что он

Class<E>, for some E, that E <: Enum<E>

но Java не позволяет вводить переменную типа E в тело метода.

Обычно мы можем использовать подстановочный знак и подстановочный знак, чтобы ввести переменную скрытого типа

class G<T extends b(T)> { ... }  // b(T) is a type expression that may contain T

G<? extends A>   --capture-->   G<T>, for some T, that T <: A & b(T)

Но это не будет работать в нашем случае, так как T в Class<T> не имеет привязки, которая заставляет его работать.

Итак, нам нужно ввести новый тип с требуемой границей

class EnumClass<E extends Enum<E>>   // called EnumValue in assylias solution

    EnumClass(Class<E> enumClass) 

    Class<E> enumClass()

EnumClass<?>   --capture-->    EnumClass<E>, for some E, that E <: Enum<E>

Затем мы вызываем EnumClass<E>.enumClass(), чтобы получить

Class<E>, for some E, that E <: Enum<E>

которого мы пытаемся достичь.

Но как мы можем назвать конструктор EnumClass? Происхождение проблемы состоит в том, что у нас нет подходящего типа для EnumClass, но конструктор EnumClass требует правильно напечатанного EnumClass.

Class<not-proper> enumClass = ...;
new EnumClass<...>(enumClass);  // wont work

К счастью (?) здесь используется тип raw, который отключает проверку типов generics

EnumClass raw = new EnumClass(enumClass);  // no generics
EnumClass<?> wild = raw; 

Итак, минимальная гимнастика, которую нам нужно выполнить, чтобы отличить класс от желаемого типа,

((EnumClass<?>)new EnumClass(enumClass)).enumClass()