Ответ 1
Мы можем упростить пример далее:
Объявление метода как
static <K,V> Map<K,V> test(Map<K,? extends V> m) {
return Collections.unmodifiableMap(m);
}
выражение
Map<SomeEnum, String> m = test(Collections.emptyMap());
может быть скомпилировано без проблем. Теперь, когда мы изменим объявление метода на
static <K extends Enum<K>,V> Map<K,V> test(Map<K,? extends V> m) {
return Collections.unmodifiableMap(m);
}
мы получаем ошибку компилятора. Это указывает на то, что разница между переносом выражения вашего потока с new EnumMap<>(…)
и new HashMap<>(…)
заключается в объявлении параметра типа для типа ключа, так как параметр типа ключа EnumMap
был объявлен как K extends Enum<K>
.
Похоже, что это связано с самореференциальной природой объявления, например, K extends Serializable
не вызывает ошибку, в то время как K extends Comparable<K>
делает.
В то время как это терпит неудачу во всех версиях javac
от Java 8 до Java 11, поведение не столь согласованно, как кажется. Когда мы меняем объявление на
static <K extends Enum<K>,V> Map<K,V> test(Map<? extends K,? extends V> m) {
return Collections.unmodifiableMap(m);
}
код может быть скомпилирован снова под Java 8, но все еще не работает с Java 9 до 11.
Для меня нелогично, что компилятор выводит SomeEnum
для K
(который будет соответствовать связанному Enum<K>
) и String
для V
, но не выводит эти типы, когда для K
указана граница. Поэтому я считаю это ошибкой. Я не могу исключить, что есть где-то в глубине спецификации утверждение, которое позволяет сделать вывод, что компилятор должен вести себя таким образом, но если это так, то спецификация также должна быть исправлена.
Как говорили другие в разделе комментариев, этот код может быть скомпилирован с Eclipse без проблем.