为什么这个Java泛型的使用无法编译?

3

我真的不明白为什么编译器不允许我执行以下操作...

import java.util.HashMap;
import java.util.Map;

public class TestMap {
   private final Map<Integer, ? extends Number> map = new HashMap<Integer, Number>();

   public void put(Integer key, Long item) {
      this.map.put(key, item);
   }
}

为什么代码this.map.put(key, item)导致编译错误?

我知道我可以将map的声明更改为使用Number而不是? extends Number来使其正常工作,但在我看来,我所做的事情是完全合法的,并且我更倾向于不允许在map中使用Number对象。我正在使用Java 1.6.0_13。


2
与您想的相反,? extends Number 将允许 Number 对象。? extends X 有界通配符意味着“X 或 X 的子类”。 - Pesto
顺便提一下,Number是一个<? extends Number>,因此,即使将其插入到通配符列表中是可行的,它也无法实现你想要的功能。Number是抽象的这一事实会实现。 - Andrew Duffy
如果你确定类型,你可以使用 ((Map<Integer, Number>) map).put(key, item);。 - Tim Büthe
感谢指出在映射中允许使用Number对象。看来我需要移除通配符,只使用Number。 - John in MD
3个回答

10

使用通配符的集合无法插入元素。这是因为,虽然List<Float>可以传递给接受List<? extends Number>参数的方法,但是往其中插入一个Long类型的元素是不安全的。在您的情况下,尽管定义了集合类型,人们可能会期望编译器知道更多,但实际上并不会。


在这里解释:http://java.sun.com/docs/books/tutorial/extra/generics/wildcards.html - Pesto
1
编译器无法允许此操作,因为它无法对地图最终传递或使用的位置负责。虽然作为人类,我们可以审查程序并说它没有以任何重要的方式使用,但编译器只能查看放置与类型声明之间的关系。 - Yishai
2
你可以插入使用通配符类型的集合。你只需要正确地做就行了。PECS规则:生产者使用extends,消费者使用super,这意味着如果你从集合中获取,则使用<? extends Number>,如果你将对象放入其中,则使用<? super Number>。如果你有List<? super Number>,你可以将Long插入其中。它允许你灵活地传递List<Object>或List<Number>等。 - Chris Lacasse
@Chris Lacasse 这是一个很好的例子,说明 Java 已经偏离了它作为易于理解的根本。 - John Topley
领导引入泛型的JSR的乔舒亚·布洛赫(Joshua Bloch)曾经表示,通配符是一个错误:http://www.artima.com/weblogs/viewpost.jsp?thread=222021 - Andrew Duffy

7
这与泛型协变有关。
当您声明时,
Map<Integer, ? extends Number> map

由于无法保证? extends NumberLong类型,因此您无法向地图中插入任何内容。

想象一下这种情况:

   Map<Integer, ? extends Number> map = new HashMap<Integer, Integer>();

   public void put(Integer key, Long item) {
      this.map.put(key, item);
   }

在这里,Integer != Long,但两者都遵循? extends Number

0

向泛型类型提供通配符可以有效地使其成为只读。


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