4

我想知道这段代码有什么问题:

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是什么?Eclipse 建议Set<?>,但我试图获得比这更具体的。

4

1 回答 1

9

这个问题在这个旧的 Apache 线程中得到了解决:

问题是该entrySet()方法返回一个 Set<Map.Entry<capture-of ? extends K, capture-of ? extends V>>与类型不兼容的Set<Map.Entry<? extends K, ? extends V>>。更容易描述为什么如果我放弃extends Kandextends V部分。所以我们有Set<Map.Entry<?, ?>Set<Map.Entry<capture-of ?, capture-of ?>>

第一个Set<Map.Entry<?, ?>>是一组不同类型的 Map.Entries - 即它是一个异构集合。它可以包含 a Map.Entry<Long, Date>和 aMap.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()不想返回它,而是“一组特定的未知类型的键和一些特定的未知类型的值的条目”。这些类型是不兼容的,因为泛型不是协变的: ASet<Type>不是 a 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);

这是一个人为的例子,因为Stringand Integerare both final,但它显示了这个概念。

一个更简单的解决方法如下:

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

这意味着不允许添加非null元素到,但在返回的情况下,无论如何都不支持and方法(感谢 newacct澄清这一点)。sSetentrySetaddaddAll

于 2013-09-20T01:09:48.053 回答