为什么具有泛型声明的 HashMap "<? super ArrayList> 不接受 put 方法中的值 "new Object()"?

Why HashMap with generic declaration "<? super ArrayList> does not accept value "new Object()" in the put method?

在处理面试问题时,我遇到了以下代码:

List<Object> list = new ArrayList();
Map<Object, ? super ArrayList> m = new HashMap<Object,  ArrayList>();
m.put(1, new Object());
m.put(2, list);

以上两个 put 方法抛出编译时错误。但是,当我添加 m.put(3, new ArrayList()); 时,它正在添加到地图中,没有编译时错误。

我很清楚,我可以将 new Object() 作为值添加到 HashMap 中,因为地图声明的类型是 < ? super ArrayList>;这意味着我可以添加任何高于 ArrayList 的值(即 ArrayListsuper)和 ArrayList 对象,但不能添加低于 ArrayList.

这一特殊概念由 Kathy Sierra 和 Bert Bates 在 SCJP 6 中写得非常好,基于该理论和示例,我认为它应该按照我的理解工作。有人可以帮助我理解错误吗?

您可以参考java规范,章节4.5.1. Type Arguments and Wildcards;它指出:

Unlike ordinary type variables declared in a method signature, no type inference is required when using a wildcard. Consequently, it is permissible to declare lower bounds on a wildcard, using the following syntax, where B is a lower bound:

? super B

下限意味着有效类型是“大于或等于”B 的所有类型(在您的情况下 ArrayList)。 ObjectList 都不大于 ArrayList 这就是编译错误的原因。

为了进一步测试这一点,尝试声明一个新的自定义类型,例如 public class MyArrayList extends ArrayList 并在您的 Map 中使用它:第二个 put 将起作用。

或者将地图声明为 Map<Object, ? super List>:同样,第二个 put 将起作用。

你无法在这样的映射中放置 Object 但是:Object 是 java class 类型的根,因此比所有内容都“小”。

编辑:在否决票后,我试图理解我是否误解了什么。此示例编译(使用标准 javac):

import java.util.*;

public class test {
  public static void main(String[] args) {
    ArrayList list = new ArrayList();
    Map<Object, ? super List> m = new HashMap<Object, List>(); 
    m.put(2, list);
  }
}

因此,下限实际上意味着您可以将所有内容设置得更大或相等。

? super ArrayList 表示 "I don't know exactly what type this is but I do know that anywhere that requires me to provide an instance of this type I can safely give it an ArrayList"。实例化通配符的实际类型可能是 ObjectCollectionListArrayList,所以你可以看到 new Object() 不能保证是安全,但 new ArrayList() 可以。

你想说的是正确的。当你只是干运行宁代码时,ArrayList 的超类的所有东西都可以作为值放在 Map 中是有意义的。

但问题是这是设计使然。通用检查只是在编译时进行,而不是在 运行 时进行。所以在编译时虽然你知道 Object 是 ArrayList 的超类;编译器不知道。所以编译器会抱怨它。唯一可以作为值的是 nullArrayList 对象本身。

? super ArrayList 并不代表 "any value that is higher than ArrayList"。意思是"any value of some unknown type ?, which is a supertype of ArrayList"。实际上,您可以在代码中看到这一点,因为 m 的值属于 ArrayList:

类型
HashMap<Object,  ArrayList> hm = new HashMap<Object,  ArrayList>();
Map<Object, ? super ArrayList> m = hm;
m.put(1, new Object());
ArrayList a = hm.get(1);

这段代码显然有问题,因为 a 应该是 ArrayList,而不是 Object

当您键入

Map<Object, ? super ArrayList> m = new HashMap<Object,
    ArrayList>();

你的“?”成为 ArrayList 所以除了 extends ArrayList

之外你不能添加任何东西

您误解了通配符 ? 的含义。你可能有一个普遍的误解:

不是意味着您可以将任何类型的对象放入地图中,该类型是ArrayList.[=15= 的超类类型]

这意味着映射中的值属于某种未知类型,是 ArrayList 的超类型。由于不知道确切的类型,编译器不允许您将 ArrayList 本身以外的任何类型的值作为映射中的值 - 编译器没有足够的信息来检查您是什么这样做是类型安全的。

假设这是允许的,那么你可以做这样的坏事:

Map<Object, ArrayList> m1 = new HashMap<Object, ArrayList>();

Map<Object, ? super ArrayList> m2 = m1;

// This should not be allowed, because m2 is really a HashMap<Object, ArrayList>
m2.put(1, new Object());

存在提供此行为的泛型。您使用通用限制映射条目。您的上限是 ArrayList,但下限对 ArrayList 的任何子类都是开放的。所以您不能将对象条目放入此映射中。

类型? super ArrayList表示未知类型,其下限为ArrayList。例如,它 可能 Object,但可能是 AbstractListArrayList.

编译器唯一可以确定的是值的类型虽然未知,但保证不会比 ArrayList 更具体,因此任何 ArrayList 的对象,或者可以添加 ArrayList 子类

还要记住:编译器只使用声明的类型;它忽略 assigned 类型。

为什么 put(1, new Object()) 失败:

显然,Object 不在范围内。

为什么 put(1, list) 失败:

变量的类型为 List,它可能包含对不在要求范围内的 LinkedList 的引用。

Map<Object, ? super ArrayList> m可以参考很多地图,比如

  • HashMap<Object, ArrayList>
  • HashMap<Object, List>
  • HashMap<Object, Object>

但是这些映射有它们自己的目的,就是准确地保存这些类型

  • ArrayList
  • List
  • Object

或其子类型,但绝不是它们的超类型,因为超类型可能没有其子类型所具有的所有必要成员(methods/fields)。

考虑这个例子

List<Banana> bananas = new ArrayList<>();//list only for bananas    <------+
List<? super Banana> list = bananas;//this is fine                         |
//                                                                         |
list.add(new Object);//error, and can thank compiler for that              |
                     //because you just tried to add Object to this list --+
                     //which should contain only Bananas
list.add(new Banana());// this is FINE because whatever precise type of list
                       // it can accept also banana

为了说明最后的评论让我们使用

List<Fruit> fruits = new ArrayList<>();//can contain Bananas, Apples, and so on
List<? super Banana> list = fruits;//again, this is fine                         
list.add(new Object)//wrong, as explained earlier
list.add(new Banana());// OK because we know that list will be of type which
                       // can also accept Banana

我相信 none 的答案给出了真正令人满意的解释。我不得不 "run to the library" 并阅读了 Joshua Bloch 的 Effective Java 的第 5 章,以最终理解它是如何工作的。

使用 Map 和原始 ArrayList,这道题有点复杂。这无助于明确问题。基本上,问题是关于以下通用声明的含义以及它们之间的区别:

Collection<? super E> x;
Collection<? extends E> y;

x = 未知类型的集合,即 E 或 E 的超类。

y = 未知类型的集合,即 E 或 E 的子类。

用例 x: E 或 E 的任何子类都可以添加到其中。

y 的用例:可以从中检索 E 或 E 的任何子类。

这也以首字母缩略词 PECS 为人所知:Producer-extends,Consumer-supers。

如果Collection应该能够'consume' E(可以添加E),声明泛型为? super E.

如果 Collection 应该能够 'produce' E,(E 可以从中检索),则将泛型声明为 ? extends E.

我希望这能澄清为什么问题中的代码无法编译:只能将 ArrayList 或 ArrayList 的子类型添加到映射中。