在阅读article时,我被困在这里。我从链接中粘贴了这个。我不明白为什么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&lt; Number&gt;不能在这里使用。的确,我们 可以尝试将该方法定义为采用List&lt; Number&gt;或列表&lt;?扩展数字&gt;,但第一个 定义将排除传入实际ArrayList&lt; Integer&gt;的可能性,而 第二个定义将禁止使用add()方法(因为有人可能会传递一个 ArrayList的&LT;浮法&GT; in,并且在调用doStuff之后会在Floats之间找到一个Integer。)
答案 0 :(得分:5)
在普通的Java中,是的,Integer
是Number
。但在泛型中,List<Integer>
不是List<Number>
。
要了解原因,请尝试将List<Integer>
分配给List<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
除外)以维护类型安全。
答案 1 :(得分:1)
Java可能有点令人困惑,因为工作中有一点双重标准。
首先,您必须考虑数组和集合都是引用类型,即它们的实例是其数据分配给堆内存并由引用指针指示的对象。
因此,数组和集合在工作中都有两种类型:对象本身的类型以及数组或集合中每个组件的类型。为了具体化,这是一个例子:
String[] strings = new String[] { "AA", "BB", "CC" };
创建的对象类型为String[]
,所有组件的类型为String
。
数组是协变,它允许JVM将对象类型和组件类型一起转换。这就是为什么这样的作业是有效的:
Object[] objects = strings;
对于数组,因为Object
是String
的超类型,所以Object[]
也是String[]
的超类型。数组是协变的。
这不适用于非数组的引用类型,例如。集合。收藏品是不变的。因此,即使Integer
是Number
的子类型,集合也是不变的,因此List<Integer>
不是List<Number>
的子类型。
答案 2 :(得分:0)
对于第一种情况,仅接受List<Number>
允许List(ArrayList
,LinkedList
,...) > Number
元素,而不是任何其他类型的List
(包括Integer
)。该列表必须在调用函数中专门输入List<Number>
(或ArrayList<Number>
或LinkedList<Number>
或...)。换句话说,列表的类型是灵活的,但通用参数不是。
在第二种情况下,使用“是a”措辞,这是有道理的。 Integer
“是”Number
,但反之并非总是如此。由于示例中的代码假设函数中使用的所有值都是整数,因此必须对泛型进行限制,以防止传递任何不太具体的值Integer
。
答案 3 :(得分:-1)
我们需要检查两件事:
void doStuff(Foo<? super Bar> foo)
Java只有一个非常简单的规则来决定Foo<A>
和Foo<B>
之间的子类型关系: none是另一个的子类型。我们说泛型类型是不变的,即使有一个基本原理,因为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()
,List<Number>
,List<Integer>
(等等)上调用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中这是非法的,但这是另一个故事)。
我在我的示例中使用的通配符有一个 upper 绑定,由extends
关键字标识。相反,您粘贴的代码段在方法签名(super
)中采用了 lower 有界通配符。实际上它可能包含一些错误,例如:
List<Integer>
传递给doStuff()
Object
list.get(index)
醇>
所以我只会告诉你签名
void doStuff(List<? super Number> arg);
代表有限列表:
void doStuff(List<Number> arg);
void doStuff(List<Object> arg);
并且您可以将Number
中的任何List<? super Number>
放在get()
中,但您只能Object
{{1}}。