我们需要检查两件事:
- 签名中的通配符是什么意思
void doStuff(Foo<? super Bar> foo)
- 方法体内的通配符是什么意思
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
实际上它可能包含一些错误,例如:
- 你不能传递
List<Integer>
给doStuff()
- 你只能
Object
从list.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
从中得到。