另一个java泛型问题

时间:2011-06-28 09:15:44

标签: java generics

我有以下课程:

interface Able{/* ... */}
class A implements Able{/* ... */}

我有

Map<String,? extends Able> as;
as = new HashMap<String, A>();

为什么以下会导致错误:

as.put("a", new A());

有什么想法吗?

6 个答案:

答案 0 :(得分:17)

对java泛型的引用很好(jdk site)。

确实@Oli_Charlesworth给出了一个很好的答案,但也许这个会更完整。

Collection<? extends Able>中,您无法插入任何正确的内容。

如果你有

class A implements Able {...}

class B implement Able {...}

然后,Collection<? extends Able>是两者的超级类型:

Collection<A>
Collection<B>

因此写一些像

这样的陈述是合法的
//Code snippet 01
Collection< ? extends Able > list;
Collection<A> listA;
Collection<B> listB;
list = listA;
list = listB;

这确实是存在通配符Collection<? extends Able>的原因。

但是,事情变得越来越有趣:

Collection<A>中,您只能插入A的对象(包括子类)。 Collection<B>也是如此。在这两者中,您无法添加仅Able的内容。例如:

//Code snippet 02
listA.add( new A() );  //valid at compile-time
listA.add( new B() );  //not valid at compile-time
listB.add( new B() );  //valid at compile-time
listB.add( new A() );  //not valid at compile-time

因此,如果您对我们在code snippets 01 & 02中看到的内容进行分组,您将理解编译器绝对不可能接受如下语句:

Collection< ? extends Able > list;
list.add( new A() );         //not allowed, will work only if list is List<A>
list.add( new B() );         //not allowed, will work only if list is List<B>

所以是的,超级类型Collection< ? extends Able >不接受添加任何内容。更一般的类型提供了子类型的功能,因此,子类型的功能较少。在这里,我们失去了添加A个对象和B个对象的能力。这些功能将在层次结构中稍后发生......甚至意味着我们无法在超类Collection< ? extends Able >中添加任何内容

补充说明:

另请注意,在Collection<Able>中,您可以添加任意内容:

Collection< Able > list;
list.add( new A() );         //valid
list.add( new B() );         //valid

但是,Collection<Able>不是Collection<A>Collection<B>的超类。与任何继承关系一样,这意味着子类可以执行超类可以执行的任何操作,因为继承是特化。因此,这意味着我们可以将A对象和B对象添加到子类Collection<A>Collection<B>,但事实并非如此。因为它不是一个你不能拥有的超类:

Collection<Able> list;
Collection<A> listA;
Collection<B> listB;
list = listA;  //not valid because there is no inheritance hierarchy
list = listB;  //not valid because there is no inheritance hierarchy

请注意,继承是一种超级联系(泛化/专业化),而集合定义了一个meronimic关系(容器/容器)。并且将它们两者正式结合起来是一件令人头疼的问题,尽管它很容易被人类模糊的生物使用,例如在法语词汇中synecdocque。 :)

答案 1 :(得分:8)

来自http://download.oracle.com/javase/tutorial/extra/generics/wildcards.html

  

像往常一样,要付出代价   为了灵活使用   通配符。那个价格是这样的   现在非法写入[基于通配符的容器]。例如,   这是不允许的:

public void addRectangle(List<? extends Shape> shapes) {
    shapes.add(0, new Rectangle()); // Compile-time error!
}
     

你应该能够找出原因   上面的代码是不允许的。类型   的第二个参数   shapes.add()? extends Shape - 是   未知的Shape子类型。因为我们   不知道它是什么类型,我们不知道   知道它是否是超类型   Rectangle;它可能会也可能不会   这样的超类型,所以它是不安全的   在那里传递Rectangle

答案 2 :(得分:3)

了解问题的一个好方法是阅读通配符的含义:

Map<String,? extends Able> as;

“具有String类型的键和一种类型的值的地图,可以扩展Able。”

不允许添加操作的原因是因为它们“打开门”在集合中引入不同类型,这会与打字系统冲突。 e.g。

class UnAble implements Able;
Map<String,UnAble> unableMap = new HashMap<String,UnAble>();
Map<String,? extends Able> ableMap = unableMap;
ableMap.put("wontwork",new A()); // type mismatch: insert an A-type into an Unable map 

正确使用通配符构造将是:

Result processAble(Map<String,? extends Able>) { ... read records & do something ... } 

Map<String,A> ableMap = new HashMap<String,A>;
ableMap.put("willwork",new A());
processAble(as);
processAble(unableMap); // from the definition above

答案 3 :(得分:1)

宣言

Map<String,? extends Able> as; 

表示“任何带有字符串键的映射,值是Able的子类型”。因此,例如,您可以执行以下操作:

Map<String,? extends Able> as = new HashMap<String, SubSubAble>();

现在让我们来看看这个例子:

Map<String,? extends Able> as = new HashMap<String, SubSubAble>();
as.put("key", new A() );

如果它是正确的,你将完成HashMap的内容{“key”,new A()} - 这是类型错误!

答案 4 :(得分:0)

您不能在使用通配符'?'声明的集合中插入任何类型的任何对象

你只能插入“null”

一旦将集合声明为List,编译器就无法知道添加SubAble是安全的。

如果已将Collection<SubSubAble>分配给Collection<Able>,该怎么办?这将是一个有效的赋值,但添加SubAble会污染集合。

How can elements be added to a wildcard generic collection?

答案 5 :(得分:0)

Collection<?> 是所有类型集合的超类型。它不是 集合,可以容纳任何类型。至少那是我对整个概念的误解。

我们可以在不关心泛型类型的地方使用它,例如:

public static void print(Collection<?> aCollection) {
  for (Object o:aCollection) {
    System.out.println(o);
  }
}

如果我们选择了签名:

public static void print(Collection<Object> aCollection)

我们只限于Collection<Object>类型的集合 - 换句话说,这样的方法不会接受Collection<String>类型的值。

因此Collection<?>类型可以采用任何类型的集合。它只需未知类型。因为我们不知道那个类型(它的未知;)),所以我们永远不能添加一个值,因为java中没有类型是未知类型的子类。

如果我们添加边界(如<? extends Able>),则类型仍然未知。

您正在寻找地图的声明,其值均实现Able接口。正确的声明就是:

Map<String, Able> map;

假设我们有两种类型AB,其子类Able和另外两张地图

Map<String, A> aMap;
Map<String, B> bMap;

并想要一个返回任何值实现Able接口的地图的方法:然后我们使用通配符:

public Map<String, ? extends Able> createAorBMap(boolean flag) {
 return flag ? aMap: bMap;
}

(再次使用约束,我们无法将新的键/值对添加到此方法返回的地图中)。