为什么具有泛型声明的 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
的值(即 ArrayList
的 super)和 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
)。 Object
和 List
都不大于 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
"。实例化通配符的实际类型可能是 Object
、Collection
、List
或 ArrayList
,所以你可以看到 new Object()
不能保证是安全,但 new ArrayList()
可以。
你想说的是正确的。当你只是干运行宁代码时,ArrayList 的超类的所有东西都可以作为值放在 Map 中是有意义的。
但问题是这是设计使然。通用检查只是在编译时进行,而不是在 运行 时进行。所以在编译时虽然你知道 Object 是 ArrayList 的超类;编译器不知道。所以编译器会抱怨它。唯一可以作为值的是 null
或 ArrayList
对象本身。
- Wildcard docs
- Compile error with generics in Map
? 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
,但可能是 AbstractList
或 ArrayList
.
编译器唯一可以确定的是值的类型虽然未知,但保证不会比 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 的子类型添加到映射中。
在处理面试问题时,我遇到了以下代码:
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
的值(即 ArrayList
的 super)和 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
)。 Object
和 List
都不大于 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
"。实例化通配符的实际类型可能是 Object
、Collection
、List
或 ArrayList
,所以你可以看到 new Object()
不能保证是安全,但 new ArrayList()
可以。
你想说的是正确的。当你只是干运行宁代码时,ArrayList 的超类的所有东西都可以作为值放在 Map 中是有意义的。
但问题是这是设计使然。通用检查只是在编译时进行,而不是在 运行 时进行。所以在编译时虽然你知道 Object 是 ArrayList 的超类;编译器不知道。所以编译器会抱怨它。唯一可以作为值的是 null
或 ArrayList
对象本身。
- Wildcard docs
- Compile error with generics in Map
? 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
,但可能是 AbstractList
或 ArrayList
.
编译器唯一可以确定的是值的类型虽然未知,但保证不会比 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 的子类型添加到映射中。