Ошибка компилятора, связанная с подстановочными знаками
Мне интересно, что не так с этим кодом:
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 за разъяснение этого момента).