2

在阅读这篇文章时,我被困在这里。我从链接粘贴这个。我不明白为什么List<Number>List<? extends Number>不能在这里使用的原因。

public void doStuff( List<Integer> list ) {     
    list.add(1);
    // do stuff
    list.get(0);    
}

We can generalize this one step further by generalizing the generic parameter:

public void doStuff( List<? super Integer> list ) {     
    list.add(1);
    // do stuff
    list.get(0);    
}

有些读者可能会问,为什么这里不能使用更直观的 List<Number>。实际上,我们可以尝试将方法定义为采用 List<Number> 或 List<? 扩展 Number>,但第一个定义将排除传入实际 ArrayList<Integer> 的可能性,而第二个定义将不允许 add() 方法(因为否则有人可能会传入一个 ArrayList<Float>,并且会发现调用 doStuff 后的浮点数之间的整数)。

4

4 回答 4

5

在普通 Java 中,是的,anIntegerNumber. 但在泛型中,List<Integer>不是List<Number>.

要了解原因,请尝试将 a 分配List<Integer>给 aList<Number>并查看会发生什么:

List<Number> numberList = new ArrayList<Integer>();  // not allowed
// This would have been allowed, even though the argument is
// boxed to a `Double`, not a `Integer`.
numberList.add(8.6); 

但是呢<? extends Number>?那不会覆盖一个List<Integer>吗?是的,但是参考文献丢失了有关Number. 如果是这样呢?

List<? extends Number> numberList = new ArrayList<Integer>();  // allowed
numberList.add(8.6);  // disallowed

原因是List<? extends Number>可以是任何扩展的东西Number,例如List<BigDecimal>. 因此它必须禁止调用该add方法(或该类中使用泛型类型参数作为该方法的参数的任何方法)(除了null)以维护类型安全。

于 2013-10-24T22:25:08.713 回答
1

Java 可能有点令人困惑,因为有一点双重标准在起作用。

首先,您必须考虑数组和集合都是引用类型,即它们的实例是对象,其数据分配到堆内存并由引用指针指示。

因此,数组和集合都有两种类型在起作用:对象本身的类型以及数组或集合中每个组件的类型。为了具体说明,这里有一个例子:

String[] strings = new String[] { "AA", "BB", "CC" };

创建的对象String[]的类型是 ,所有组件的类型是String

数组是协变的,它允许 JVM 将对象类型和组件类型一起转换。这就是为什么这样的分配是有效的:

Object[] objects = strings;

对于数组,因为Object是 的超类型String, thenObject[]也是 的超类型String[]。数组是协变的。

这不适用于不是数组的引用类型,例如。收藏品。集合是不变的。因此,即使 aInteger是 的子类型Number,集合也是不变的,因此List<Integer>不是 的子类型List<Number>

于 2013-10-24T22:42:11.453 回答
0

对于第一种情况,接受 aList<Number> 允许List( ArrayList, LinkedList, ...) 个Number元素,而不是List任何其他类型的 a (包括Integer)。该列表必须在调用函数中专门键入为List<Number>(或ArrayList<Number>,或LinkedList<Number>,或...)。换句话说,列表的类型是灵活的,但通用参数不是。

在第二种情况下,使用“是一个”的措辞,这是有道理的。“Integer是” Number,但反过来并不总是正确的。由于示例中的代码假设函数中使用的所有值都是整数,因此它必须对泛型设置一个限制,以防止任何不具体的东西Integer被传入。

于 2013-10-24T22:59:40.540 回答
-1

我们需要检查两件事:

  1. 签名中的通配符是什么意思void doStuff(Foo<? super Bar> foo)
  2. 方法体内的通配符是什么意思

Java只有一个非常简单的规则Foo<A>来确定和之间的子类型关系Foo<B>没有一个是另一个的子类型。我们说泛型类型是不变的,即使 Java 设计者这样做是有理由的,但从您的角度来看,这是一个任意决定。

是尖括号让我们感到困惑,可怜的开发者:我们可以毫无问题地接受它FooBar,并且FooQoo没有任何关系;但出于某种原因,我们需要相信这Foo<Qoo>一点Foo<Bar>。不,事实并非如此。

无论 A 和 B 如何相互关联,X<A>并且X<B>不相关。

无论 A 和 B 如何相互关联,X<A>并且X<B> 不相关。

无论 A 和 B 如何相互关联,X<A> 并且X<B>不相关。

一旦您确信上述内容,请注意以下代码段:

List<Double> doubles = ...;
List<Integer> integers = ...;

Number firstDouble = doubles.get(0);
Number firstInteger = integers.get(0);

调用get(0)这两个列表为我们提供了一个与数字兼容的对象。我们可能希望将调用放在get()类似的方法中,getFirstOfList(list)但我们刚刚了解到这样的方法不存在,因为它会接受两种完全不相关的类型。

这就是通配符发挥作用的地方!我们观察到调用get()a List<Number>、 a List<Integer>、 a List<Double>(等等)会返回一个与数字兼容的对象(即Number或其子类型),因此必须有一种方法可以在语言级别上表达这一点。Java 设计者给了我们通配符,它​​的作用是这样的:当你声明

public void doStuff(List<? extends Number> arg);

它与声明以下无限列表具有相同的效果:

public void doStuff(List<Number> arg);
public void doStuff(List<Integer> arg);
public void doStuff(List<Double> arg);
public void doStuff(List<Float> arg);
public void doStuff(List<BigDecimal> arg);
...

如果没有通配符的设备,您必须为每种支持的列表类型编写一个方法(顺便说一句,这在 Java 中是非法的,但这是另一回事)。

我在示例中使用的通配符有一个上限extends,由关键字标识。相反,您粘贴的代码片段在方法签名 ( )中采用了下界通配符。super实际上它可能包含一些错误,例如:

  1. 你不能传递List<Integer>doStuff()
  2. 你只能Objectlist.get(index)

所以我会告诉你签名

void doStuff(List<? super Number> arg);

代表有限列表:

void doStuff(List<Number> arg);
void doStuff(List<Object> arg);

你可以把任何Number你喜欢的东西放在 a 中,List<? super Number>但你只会get() Object从中得到。

于 2013-10-24T23:48:58.123 回答