Java HashMap与ArrayList通配符

4

我有一个HashMap,其中值是ArrayLists,我正在尝试编写一个函数来接受这些HashMap的通用实例

HashMap<String, ArrayList<Integer>> myMap = new HashMap<String, ArrayList<Integer>>();
public static void foo(HashMap<?, ArrayList<?>> a) {}
public static void bar(HashMap<?, ? extends ArrayList<?>> a) {}

// Compilation Failure!
foo(myMap);

// This works, but why do I need ? extends ArrayList
bar(myMap)

错误信息为:
方法 Example 中的 foo(HashMap>) 不适用于参数 (HashMap>)
为什么需要在 extends ArrayList 中使用通配符?
我认为通过使用 ArrayList(不带 ? extends),可以将函数限制为只接受值为 ArrayList 的 HashMap。
同时,我知道以下泛型方法可行:
public static <K,V> void printer(HashMap<K, ArrayList<V>> list) { }

这种行为方式和我之前对 ArrayList<?> 的理解是一致的。有人能解释一下其中的微妙差别吗?


请告诉我们由 foo 生成的确切编译器错误,或者给出导致错误的 foo 内部代码示例。实际上,这两个信息都会非常有帮助。 - Ken Wayne VanderLinde
The method foo(HashMap<?,ArrayList<?>>) in the type Example is not applicable for the arguments (HashMap<String,ArrayList<Integer>>) - Michael Brewer-Davis
我得到的错误和Michael Brewer一样。 - Kevin
3个回答

6
通配符语法被故意设计成(误导)人们认为它匹配任何类型。这对于简单的情况是可行的。
List<?> <= List<String> // OK, right is subtype of left

但请记住,它仅适用于第一层级,而不是更深的层级。
List<List<?> <= List<List<String>>  // FAIL

这是好的

List<? extends List<?>> <= List<List<String>>

由于新的一级?,因此如果ST的子类型,则G<S>G<? extends T>的子类型。将其应用于S=List<String>T=List<?>的情况。


你有这方面的引用吗?我确实验证了这一点,但我只是好奇它在哪里有记录。 - Bala R
好的。还可以在以下链接中找到一个类似的答案,其中有很多参考资料:https://dev59.com/onA65IYBdhLWcg3w5y_B#3575895 - Bala R
Bala R:在谷歌上的第一个搜索结果有一个很好的教程:http://download.oracle.com/javase/tutorial/extra/generics/wildcards.html - Mathias Schwarz
在JLS3中,#4.10.2子类型规则中只涉及到第一级通配符。另外,在#5.1.10中,“捕获转换不递归应用”。 - irreputable

1
  1. HashMap<?, ArrayList<?>>类型表示的是一个映射,将某些未知(固定)类型的键映射到列表,每个列表都有一些未知的固定类型的元素。

  2. 这样一个映射的具体实例可以是:

    new HashMap<String, ArrayList<?>>();
    

    这是一个我们可以添加任意ArrayList作为值的映射,对于我们从中获取的值(列表),我们不知道参数类型。

  3. HashMap<String, ArrayList<Integer>>类型表示的是从字符串到整数列表的映射。这是一个我们只能添加整数ArrayList作为值的映射,而不能添加任意ArrayList。因此,它不能是类型2的子类型(因为它允许的比类型2少)。

    (类型1是类型2的超类型,也让键类型未知。)

  4. HashMap<String, ? extends ArrayList<?>>类型表示的是从字符串到某个未知类型的映射,该类型仅已知是ArrayList<?>的子类型。在这样的映射中,我们无法插入任何东西(作为值),但我们从中获取的所有值都保证是ArrayList<?>类型,即某些未知的东西的数组列表(每个列表可能是另一些东西)。我们可以插入新的字符串键,但只能使用null值。

    类型2和类型3都是类型4的子类型(因为ArrayList<?>ArrayList<Integer>都满足extends ArrayList<?>条件),但它们不是彼此的子类型。

  5. HashMap<?, ? extends ArrayList<?>>与类型4类似,但我们也不知道键类型。这意味着我们实际上不能对映射做任何事情,除了迭代它(或查找键的值-get()接受一个Object参数,而不是K)并删除内容。对于我们检索到的值,我们只能迭代和删除内容,不能插入或重新排序任何内容。(即类型4是类型5的子类型。)

  6. 方法参数类型HashMap<K, ArrayList<V>>具有类型变量<K,V>,表示的是从某种类型K到某种类型V的列表的映射,其中K和V由方法的调用者决定。类型3显然符合这个条件(使用<String,Integer>),因此您可以调用此方法。您实际上无法将新键放入映射中,因为您的方法不知道K是什么,但是您可以将现有键映射到新值。您可以使用new ArrayList<V>()(并将这些元素作为值放入映射中),但只能将现有列表中的现有V对象放入这些列表中。比类型5更多可能性。


只是一点小建议:对于您的方法,应该使用更通用的类型Map<K,V>List<E>,而不是HashMap<K,V>ArrayList<E>,因为这个方法很可能适用于任何类型的映射/列表,并且并不关心具体实现。您也可以使用
Map<String, List<Integer>> myMap = new HashMap<String, List<Integer>>();

声明并初始化您的变量。(您仍然可以将ArrayList<Integer>对象作为值放入其中。)


0

非常简单。即使 VW 的子类型,HashMap<K, V> 也不是 HashMap<K, W> 的子类型。(你已经知道这一点了,对吧?) 在这里,VArrayList<Integer>,而 WArrayList<?>。(重要的是它们是不同的。) 是的,ArrayList<Integer>ArrayList<?> 的子类型,但这是我们之前讨论的内容,也就是说这并不重要。然而,由于通配符的存在,HashMap<K, V>HashMap<K, ? extends W> 的子类型。


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