泛型中 ? super T 和 ? extends T 的区别

JAVA herman 1504浏览 0评论
公告:“业余草”微信公众号提供免费CSDN下载服务(只下Java资源),关注业余草微信公众号,添加作者微信:xttblog,发送下载链接帮助你免费下载!
本博客日IP超过1800,PV 2600 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog,之前的微信号好友位已满,备注:返现
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
视频教程免费领

<? super T>表示包括T在内的任何T的父类,<? extends T>表示包括T在内的任何T的子类,下面我们详细分析这两种通配符的区别

extends

List<? extends Number> foo3的通配符声明,意味着以下的赋值都是合法的:

// Number "extends" Number (in this context)

List<? extends Number> foo3 = new ArrayList<? extends Number>(); 

// Integer extends Number

List<? extends Number> foo3 = new ArrayList<? extends Integer>();

// Double extends Number

List<? extends Number> foo3 = new ArrayList<? extends Double>();

读取操作通过以上给定的赋值语句,一定能从foo3列表中读取到的元素的类型是什么?

  • 能读取到Number,因为以上的列表要么包含Number元素,要么包含Number的类元素。
  • 不能保证读取到Integer,因为foo3可能指向的是List<Double>。
  • 不能保证读取到Double,因为foo3可能指向的是List<Integer>。

写入操作通过以上给定的赋值语句,能把什么类型的数据合法插入到foo3中?

  • 不能插入一个Integer元素,因为foo3可能指向List<Double>。
  • 不能插入一个Double元素,因为foo3可能指向List<Integer>。
  • 不能插入一个Number元素,因为foo3可能指向List<Integer>。

不能往List<? extends T>中插入任何类型的对象,因为不能保证列表实际指向的类型是什么,并不能保证列表中实际存储什么类型的对象。唯一可以保证的是,可以从中读取到T或者T的子类。

super

List<? super Integer> foo3的通配符声明,意味着以下赋值是合法的:

// Integer is a "superclass" of Integer (in this context)

List<? super Integer> foo3 = new ArrayList<Integer>();

// Number is a superclass of Integer

List<? super Integer> foo3 = new ArrayList<Number>();

// Object is a superclass of Integer

List<? super Integer> foo3 = new ArrayList<Object>();

读取操作以上给定的赋值语句,一定能从foo3列表中读取到的元素是什么?

  • 不能保证读到Integer,因为foo3可能指向List<Number>或者List<Object>。
  • 不能保证读到Number,因为foo3可能指向List<Object>。

唯一可以保证的是,你可以读取到Object或者Object子类的对象,并不知道具体的子类是什么。

写入操作通过以上给定的赋值语句,能把什么类型的元素合法的插入到foo3中?

  • 能插入Integer对象,因为上述声明的列表都支持Integer。
  • 能插入Integer的子类对象,因为Integer的子类同时也是Integer,原因同上。
  • 不能插入Double对象,因为foo3可能指向ArrayList<Integer>。
  • 不能插入Number对象,因为foo3可能指向ArrayList<Integer>。
  • 不能插入Object对象,因为foo3可能指向ArrayList<Integer>。

PECS

请记住PECS原则:生产者(Producer)使用extends,消费者(Consumer)使用super。

(1)生产者使用extends
如果需要一个列表提供T类型的元素(即你想从列表中读取T类型的元素),你需要把这个列表声明成<? extends T>,比如 List<? extends Integer>,因此你不能往该列表中添加任何元素。
(2)消费者使用super
如果需要一个列表使用T类型的元素(即你想把T类型的元素加入到列表中),你需要把这个列表声明成<? super T>,比如 List<? super Integer>,因此你不能保证从中读取到的元素的类型。
(3)既是消费者也是生产者
如果一个列表既要消费,又要生产,那就不能使用泛型通配符声明列表,要使用明确的泛型,比如 List<Integer>。

以下是JDK8中java.util.Collections中copy的代码:

public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    int srcSize = src.size();
    if (srcSize > dest.size())
        throw new IndexOutOfBoundsException("Source does not fit in dest");

    if (srcSize < COPY_THRESHOLD ||
        (src instanceof RandomAccess && dest instanceof RandomAccess)) {
        for (int i=0; i<srcSize; i++)
            dest.set(i, src.get(i));
    } else {
        ListIterator<? super T> di=dest.listIterator();
        ListIterator<? extends T> si=src.listIterator();
        for (int i=0; i<srcSize; i++) {
            di.next();
            di.set(si.next());
        }
    }
}

从代码中可以看到作为生产者的src的声明是List<? extends T>,作为消费者的dest的声明是List<? super T>。可以再用一句话简单的便于理解,假如T是Number,如果生产者生产Integer类型,那么消费者消费Object类型,明显消费者可以消费;如果生产者生产Object类型,那么消费者消费Integer类型,明确消费者不可以消费;可以看到Object > Number > Integer

业余草公众号

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加QQ1群:135430763(2000人群已满),QQ2群:454796847(已满),QQ3群:187424846(已满)。QQ群进群密码:xttblog,想加微信群的朋友,之前的微信号好友已满,请加博主新的微信号:xttblog,备注:“xttblog”,添加博主微信拉你进群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作可添加助理微信进行沟通!

本文原文出处:业余草: » 泛型中 ? super T 和 ? extends T 的区别