Java泛型中的通配符<?>,<? extends T>,<? super T>

之前一直对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 不是 A 的子类。这个时候就可以使用通配符来解决,我们修改 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 通配符的类型更适合存入资源。

当然,如果你既想设置值又想取出值,那么就不适合使用通配符了。

发布了8 篇原创文章 · 获赞 7 · 访问量 712

猜你喜欢

转载自blog.csdn.net/hao_yan_bing/article/details/89476992