Ошибка компилятора, связанная с подстановочными знаками

Мне интересно, что не так с этим кодом:

Map <? extends String, ? extends Integer> m = null;
Set<Map.Entry<? extends String, ? extends Integer>> s = m.entrySet();

Компилятор жалуется с сообщением об ошибке:

Несоответствие типов: невозможно преобразовать из Set<Map.Entry<capture#1-of ? extends String,capture#2-of ? extends Integer>> в Set<Map.Entry<? extends String,? extends Integer>>

Какой тип должен s быть? Затмение предлагает Set<?> но я пытаюсь получить более конкретную информацию.

1 ответ

Эта проблема решена в этой старой ветке Apache:

Проблема в том, что entrySet() метод возвращает Set<Map.Entry<capture-of ? extends K, capture-of ? extends V>>, который несовместим с типом Set<Map.Entry<? extends K, ? extends V>>, Проще описать почему, если я уроню extends K а также extends V часть. Итак, мы имеем Set<Map.Entry<?, ?> а также Set<Map.Entry<capture-of ?, capture-of ?>>,

Первый, Set<Map.Entry<?, ?>> это набор Map.Entries разных типов - т.е. это гетерогенная коллекция. Это может содержать Map.Entry<Long, Date> и Map.Entry<String, ResultSet>> и любая другая пара типов, все в том же наборе.

С другой стороны, Set<Map.Entry<capture-of ?, capture-of ?>> является однородной коллекцией той же (хотя и неизвестной) пары типов. Например, это может быть Set<Map.Entry<Long, Date>> поэтому все записи в наборе ДОЛЖНЫ быть Map.Entry<Long, Date>,

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

Итак, убирая границы для простоты, объявляя

Map<?, ?> m;

означает "карту некоторого определенного неизвестного типа ключей и некоторого определенного неизвестного типа значений".

Но декларируя

Set<Map.Entry<?, ?>> s;

означает "набор записей любого типа ключа и значения".

Так вот, где вы столкнетесь с проблемами, потому что выражение m.entrySet() не хочет возвращать это, но вместо этого "набор записей некоторого определенного неизвестного типа ключей и некоторого определенного неизвестного типа значений". И эти типы несовместимы, потому что генерики не ковариантны: Set<Type> не является Set<SuperType>,

(См. Этот увлекательный пост, который помогает отделить нюансы вложенных подстановочных знаков: несколько подстановочных знаков в универсальных методах делают компилятор Java (и меня!) Очень запутанным.)

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

private <K extends String, V extends Integer> void help(final Map<K, V> map) {
    final Set<Map.Entry<K, V>> entries = map.entrySet();
    // logic
}

...

Map<? extends String, ? extends Integer> m = null;
help(m);

Это надуманный пример, так как String а также Integer оба final, но это показывает концепцию.

Более простой обходной путь заключается в следующем:

Set<? extends Map.Entry<? extends String, ? extends Integer>> s = m.entrySet();

Это означает добавление null элементы к s не допускается, но в случае Set вернулся entrySet, add а также addAll в любом случае методы не поддерживаются (спасибо newacct за разъяснение этого момента).

Другие вопросы по тегам