this old Apache thread 解决了这个问题:
问题是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>.
问题的症结在于顶级通配符capture,这意味着它们本质上是一次性类型参数。相比之下,嵌套通配符不捕获,并且具有某种不同的含义。
所以,为了简单起见,去掉边界,声明
Map<?, ?> m;
表示“一些特定未知类型的键和一些特定未知类型的值的映射”。
但是声明
Set<Map.Entry<?, ?>> s;
表示“任何类型的键和值的一组条目”。
这就是你遇到麻烦的地方,因为表达式 m.entrySet() 不想返回它,而是“一组某些特定未知类型的键和一些特定的条目 未知类型的值”。这些类型不兼容,因为泛型 aren't covariant: A Set<Type> 不是 Set<SuperType>。
(请参阅这篇引人入胜的帖子,它有助于梳理嵌套通配符的细微差别:Multiple wildcards on a generic methods makes Java compiler (and me!) very confused。)
一种解决方法是使用capture helper 方法,它利用了可以嵌套形式类型参数的事实:
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,但是对于entrySet 返回的Set,无论如何都不支持add 和addAll 方法(谢谢clarifying this point 的 newacct)。