Java泛型类型与通配符

3
我想编写一个能够接受参数的方法:
Map<String, [the type here could be anything]>

所以,这就是我编写的代码:

/**
 * Get list value from a map, empty list if there's no key matched 
 */
private List<? extends Object> getListValueFromMap(Map<String, List<? extends Object>> map, String key) {
    List<? extends Object> list = map.get(key);
    return list == null ? EMPTY_LIST : list;
}

EMPTY_LIST的定义如下:
List<Object> EMPTY_LIST = Collections.unmodifiableList(new ArrayList<Object>());

问题在于当我使用以下代码调用该方法时,Eclipse告诉我出现错误,即给定参数的类型不适用。

getListValueFromMap(cityNameMap, "key");

错误信息如下:

ChinaAreaResource 类中的方法 getListValueFromMap(Map<String,List<? extends Object>>, String) 对于参数 (Map<String,List<City>>, String) 不适用。

我在哪里出错了?

这是 cityNameMap 的定义:

/** map city's name to city list */
private Map<String, List<City>> cityNameMap;

citiNameMap 是如何定义的? - Konstantin Yovkov
对我来说,像这样的 cityNameMap Map<String, List<? extends Object>> cityNameMap = null; 运行良好。 - Thrash Bean
Collections.unmodifiableList(new ArrayList<Object>()); 的意义是什么?如果你已经知道 Collections,为什么不使用 Collections.emptyList() 呢? - Holger
2个回答

4

重要的是要注意在泛型中 ? extends Object 实际上代表什么。

在其当前形式下,它是一个上边界通配符,其上边界为 Object。理想情况下,这将意味着您可以使用其中的任何列表,但由于泛型是不变的,这在这里并不是真的 - 您只能使用具有相同边界的列表。

由于列表的包含类型可能从映射到映射而变化,请在泛型类型上设置边界以访问您关心的列表。

private <T> List<? extends T> getListValueFromMap(Map<String, List<T>> map, String key) {
    List<? extends T> list = map.get(key);
    return list == null ? EMPTY_LIST : list;
}

请确保相应地更新您的EMPTY_LIST依赖项;显然,在这种状态下,您无法返回绑定了Object的列表。
这样做的好处是允许您使用任何提供的类型传递列表,并且由于列表上的泛型参数的处理方式,一旦从映射中检索到它们,您将无法简单地向其中添加元素。

我认为我有点理解你的话,因为编译器无法确定 <? extends T> 的真实类型,它只允许从中检索出 T 类型。 - bedew
空列表的正确习惯用法是Collections.<T>emptyList(),它仍会返回单例Collections.EMPTY_LIST,但现在不需要抑制“unchecked”警告。从Java 8开始,你可以省略<T>直接使用Collections.emptyList()。顺便说一句,在某些情况下,泛型并不具有不变性,因此使用Map<String, ? extends List<?>>作为参数类型也可以解决问题,但添加类型参数<T>以声明返回的列表具有与Map值类型相同的元素类型要好得多。 - Holger
顺便说一下,在此过程中将地图的 List<T> 转换为 List<? extends T> 没有任何好处。您只需返回 List<T> 即可... - Holger
@Holger:是的,但是根据OP的泛型设置,他似乎希望那个列表成为一个生产者,所以我只是保留了这种行为。List<T>List<? extends T>之间的关键区别在于你不能向后者添加元素;你只能检索它们。 - Makoto
不应该依赖于由通用签名产生的不可变性。Collections.swap仍然可以调用这样的列表,remove也是如此。因此,我建议遵循Oracle的建议:“应避免将通配符用作返回类型,因为它会强制程序员处理通配符”。 - Holger

0
你可以这样做:
private <T> List<T> getListValueFromMap(Map<String, List<T>> map, String key) {
    //
}

在我的IDE(IntelliJ 2016.1.1)中,两者都没有任何问题,但可能是因为你的设置不同。


是的,那可以解决我的问题,只是我不能从方法中返回EMPTY_LIST,除非我保持返回类型不变,但我认为真正的原因可能在于,存在一些设计问题,即使调用者在参数Map中给我什么类型的List,我都只返回一个单例实例List<Object>。 - bedew
我仍然很好奇为什么我不能调用原始方法~ - bedew
公共静态常量列表 EMPTY_LIST = new ArrayList(); 对我来说运行良好。 - delucasvb
如果您仍然想使用您的代码,请参考Makoto的答案获取更多信息。您可以尝试像这样声明您的cityNameMap:Map<String,List <? extends Object>> cityNameMap - delucasvb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接