之前一直对Java泛型中的通配符不是很清楚,前几天专门研究了一下。
Java中的泛型通配符分为以下三种:
- <? extends T> 子类型限定通配符
- <? super T> 超类型限定通配符
- <?> 无限定通配符
通配符的使用场景
通配符只有在修饰一个变量或参数的时候会用到,在定义泛型类或泛型方法的时候是不能使用通配符的。
为了更好的说明泛型通配符的使用,我们使用代码示例来加以说明。首先我们创建一个类 A,是一个泛型类,里面保存一个变量 value
public class A<T> {
private T value;
// 省略 get 和 set 方法
// ......
}
我们再创建两个类 Father 和 Son,Son 是 Father 的子类
// Father.java
public class Father {
}
// Son.java
public class Son extends Father{
}
思考下面的情况
public static void main(String[] args){
A<Father> a1 = new A<Father>();
A<Son> a2 = new A<Son>();
test(a1);
test(a2); // 编译错误
}
public void test(A<Father> a){...}
Son 是 Father 的子类,但 test(A<Father> a)
方法却不能接收参数 a2,也就是说 A<Son> 不是 A<Father> 的子类。这个时候就可以使用通配符来解决,我们修改 test 方法
public static void main(String[] args){
A<Father> a1 = new A<Father>();
A<Son> a2 = new A<Son>();
test(a1);
test(a2);
}
public void test(A<? extends Father> a){...}
这样可以可以正常调用,说明类型 A<Son>
是 A<? extends Father>
的子类型
我们清楚了通配符的使用场景,下面再分别看下几种通配符
<? extends T> 子类型限定通配符
我们上面的例子使用了子类型限定通配符,使用通配符很方便,但也带来了一些问题,我们接着看代码
public void test(A<? extends Father> a){
a.setValue(new Father()); //编译错误
a.setValue(new Son()); //编译错误
Father father = a.getValue();
}
你会发现我们根本不能调用 setValue 方法,假想一下 A<? extends Father> 类,里面的方法似乎是这样的
// 这不是真正的Java方法,只是为了说明
? extends Father getValue();
void setValue(? extends Father);
当我们调用 setValue 方法的时候,编译器只知道需要某个 Father 及其子类型,但不知道具体是什么类型,所以它拒绝传递任何的特定类型。
使用 getValue 就不会有问题,因为返回值肯定是 Father 及其子类型,所以我们把返回值赋给一个 Father 的引用完全合法。
<? super T> 超类型限定通配符
超类型限定通配符的行为与上面说的子类型限定通配符相反,可以为方法提供参数,但不能使用返回值。我们看下面的例子:
public void test2(A<? super Father> a){
a.setValue(new Father());
Father father = a.getValue(); // 编译错误
Object object = a.getValue();
}
假想一下 A<? super Father> 类,里面的方法似乎是这样的
// 这不是真正的Java方法,只是为了说明
void setValue(? super Father)
? super Father getValue()
setValue 方法不知道参数的具体类型,但是可以确定的是参数肯定是 Father 及其父类型,所以我们传递 Father 及其子类型是合法的。
调用 getValue 方法不能保证返回类型的对象,所以只能赋给一个 Object。
<?> 无限定通配符
还可以使用无限定通配符 <?> ,这种方式,不能为方法提供参数,调用方法返回值也只能赋给 Object。
public void test3(A<?> a){
Object object = a.getValue();
a.setValue(new Object()); //编译错误
}
getValue 的返回值只能赋给一个 Object,setValue 方法不能被调用(注意:可以调用 setValue(null))。
所以感觉无限定通配符是集合了上面说的两种通配符的缺点,那我们为什么还要使用它呢?其实在某些简单的场景还是有用的,例如下面这种情况
// 判断A类中的值是否为空,并不关心A类中值具体是什么类型
public Boolean isNull(A<?> a){
return a.getValue == null;
}
总结
看完了三种通配符的使用,我们来做个总结:
- <? extends T> 子类型限定通配符
无法向其中设置值,但是可以进行正常的取出 - <? super T> 父类型限定通配符
可以设置 T 类型及其子类型的对象,但是取出的时候只能赋值给 Object - <?> 无限定通配符
无法向其中设置值,取值的时候也只能赋值给 Object
从上面的总结可以看出,<? extends T> 通配符偏向于内容的获取,而 <? super T> 通配符更偏向于内容的存入。
PECS 原则(Producer Extends Consumer Super)很好的解释了这两种通配符的使用场景:
- Producer Extends 说的是当你的情景是生产者类型,需要获取资源以供生产时,建议使用 extends 通配符,因为使用了 extends 通配符的类型更适合获取资源。
- Consumer Super 说的是当你的场景是消费者类型,需要存入资源以供消费时,建议使用 super 通配符,因为使用 super 通配符的类型更适合存入资源。
当然,如果你既想设置值又想取出值,那么就不适合使用通配符了。