通配符 ? 与 T 的区别
T
:作用于模版上,用于将数据类型进行参数化,不能用于实例化对象。?
:在实例化对象的时候
,不确定泛型参数的具体类型时,可以使用通配符进行对象定义。(有点像 C++ 的 auto、C 语言的 void * 指针)
<T>
等同于 <T extends Object>
<?>
等同于 <? extends Object>
<? super MyClass>
以 set
的角度去看待。
MyClass
是我们知道的提示(一个边界):
- 不允许调用 get 方法获得 MyClass 的引用(这里是指 MyClass 的 get/set 方法)
- 因为你不知道用什么去「接收/容纳」这个
? super MyClass
(不能确定上界),只能用Object
。1
Object abc = <? super MyClass>.get();
- 因为你不知道用什么去「接收/容纳」这个
- 允许调用 set 方法传入 MyClass 的引用
- 因为
<? super MyClass>
表示的类对象不是 MyClass,就是它的父亲。(作为「左值」)1
<? super MyClass>.set(XXX); // XXX 可以是 MyClass,或者它的子类
- 因为
当使用 <? super MyClass>
的时候,表明未知类的继承结构处于 Object 和 MyClass 之间,这时编译器只能确定任何返回该未知类型的方法,返回的变量都是 Object 的子类,所以返回的类型就只能确定为 Object,比如 getter 方法。(函数返回的时候,或者赋值给左边的值的时候)
此外,set
的情况还包括使用该未知类型作为参数的方法,该参数一定是 MyClass 或者其父类,所以可以传递 MyClass 及其子类进去。
例子:
1 | public class Collections { |
<? extends MyClass>
以 get
的角度去看待。
MyClass
是我们知道的提示(一个边界):
- 允许调用 get 方法获得 MyClass 的引用
1
MyClass clz = <? extends MyClass>.get(); // 因为我们知道 MyClass 是上界
- 不允许调用 set 方法传入 MyClass 的引用
1
<? extends MyClass>.set(XXX); // 但我们不知道下界是什么,只能取个极限 null(XXX = null)
使用 <? extends MyClass>
的时候,未知类型一定是 MyClass 的子类,但向下延伸到无穷尽,无法判断。所以返回未知类型的方法的返回类型有一个上界,就是 MyClass,即返回类型确定为 MyClass。但是使用未知类型的方法,因为向下继承无限延伸,无法判断下界,所以不能使用该方法。
<?> 通配符
使用 <?>
的时候,可以当作 <? extends Object>
,即上界是 Object。
可以使用 get 方法:
1 | Object abc = <? extends Object>.get(); |
不可以使用 set 方法(或者只能 set null):
1 | <? extends Object>.set(XXX); // 无法推断 ? 的下界,所以也无法确定 XXX 的上界。 |
注意:Pair<?>
和 Pair
不同(不要想当然以为 Pair<?>
可以被放入任何东西!)
Pair<?>
相当于Pair<? extends Object>
Pair<Object>
可以调用 set 方法放入任何东西
例子:
1 | public class Pair<T> { ... } |
无限定通配符 <?>
很少使用,可以用 <T>
替换它:
其它快速判断的方法
get-put principle
选择限定通配符时的快速判断方法(get-put principle):
- Use an
extends
wildcard when you onlyget values
out of a structure. - Use a
super
wildcard when you onlyput values
into a structure. Don't use
a wildcard when you do both.
PECS
PECS(Producer,Extends,Consumer,Super)来源于 Effective Java。
Producer(extends)
这里是生产者的意思。当你要从某个参数中获取某个类型的数据,那么应该声明这个参数类型为
<? extends T>
。比如,List<? extends Number> list
表明 list 是一个生产者,你可以从其中取出 Number 对象(或者其实是子类,但是你不一定知道)。
(等同于 get values)Consumer(super)
这里是消费者的意思。当某个参数将要消费(使用)到某个类型的数据,那么应该声明这个参数类型为
<? super T>
。比如,Collection<? super E> collect
表明 collect 可以消费(使用)或者被放入类型为 E(甚至是子类)的数据。既要生产又要消费,那就不要使用通配符了。