Связанная с компилятором ошибка, связанная с шаблоном
Мне интересно, что не так с этим кодом:
Map m = null; Set<Map.Entry> s = m.entrySet();
Компилятор жалуется на сообщение об ошибке:
Тип несоответствия: невозможно преобразовать из
Set<Map.Entry>
Set<Map.Entry>
Set<Map.Entry>
дляSet<Map.Entry>
Set<Map.Entry>
Каким должен быть тип s
? Eclipse предлагает Set
Но я пытаюсь получить более конкретную информацию.
Эта проблема решается в этом старом streamе Apache :
Проблема в том, что метод
entrySet()
возвращаетSet
> Set
> Set
, что несовместимо с типом> Set
> Set
> Set
. Легче описать, почему, если я отбрасываю> extends K
иextends V
часть. Итак, у нас естьSet
ИSet
.> Первый,
Set
– это набор Map.Entries разных типов – то есть это гетерогенная коллекция. Он может содержать> Map.Entry
иMap.Entry
и любую другую пару типов, все в одном наборе.> С другой стороны,
Set
является однородной коллекцией той же (хотя и неизвестной) пары типов. Например, это может быть> Set
, поэтому все записи в наборе ДОЛЖНЫ быть> Map.Entry
.
Суть проблемы заключается в том, что захват групповых символов верхнего уровня, что означает, что они по сути являются одноразовыми параметрами. Напротив, вложенные подстановочные знаки не захватывают и имеют несколько иное значение.
Итак, удаляя оценки для простоты, объявляя
Map, ?> m;
означает «карту определенного неизвестного типа ключей и некоторого определенного неизвестного типа значений».
Но объявление
Set> s;
означает «набор записей любого типа ключа и значения».
Так вот где вы столкнулись с проблемой, потому что выражение m.entrySet()
не хочет возвращать это, а вместо этого «набор записей какого-то определенного неизвестного типа ключей и некоторых определенных неизвестных типов значений». И эти типы несовместимы, потому что generics не ковариантны : A Set
не является Set
.
(См. Этот увлекательный пост, который помогает разделить нюансы вложенных подстановочных знаков. Множественные подстановочные знаки для общих методов делают Java-компилятор (и меня!) Очень запутанным .)
Одним из способов является использование метода захвата-захвата , который использует тот факт, что параметры формального типа могут быть вложенными:
private void help(final Map map) { final Set> 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 для уточнения этой точки ).