List<? extends T>与List<? super T>的区别

大家好,我是一安,今天聊一下 List<? extends T>与List<? super T>的区别。

前置名词解释

  • ?表示类型通配符,即具体传什么参数类型,在List定义时不用考虑
  • <T>这里的<>表示泛型,T表示泛型中装载的类型为T类型,等到需要的时候,我们可以具体这个T。我们在使用动态数组实现ArrayList的时候,如果希望这个ArrayList不仅仅支持一个类型的话,我们可以给这个ArrayList定义泛型,泛型中存放的是T类型。在实际创建出这个ArrayList对象时,我们可以指定泛型中的具体类型
  • <? extends T>类型上界,这里的?可以是T类型或者T的子类类型
  • <? super T>类型下界,这里的?可以是T类型或者T的超类类型,但不代表我们可以往里面添加任意超类类型的元素

在List中引入通配符界限限制的假设

不管是List<? extends T>还是List<? super T>,如果能读取元素,那么这个元素一定能转化为T类型,注意不是强制类型转换,强制类型转换是容易出现问题。

显然List<? extends T>内都是T的子类类型,能够向上转型为T类型,因此该list可以读取。

而List<? super T>内可以是T的超类类型,T的超类转T是有可能出现异常的。

那我干脆转化成Object类型不好吗,所有类的基类都是Object,不属于强制类型转换。哥们,转换成Object了,那你还图个啥?转换为Object类型是没有意义的。

假设List<? extends T>能添加元素,那么需要满足添加的任意元素需要能够直接转化成T的任何一个子类,T的子类A和子类B是不能相互转化的,显然该list是不能添加元素的。

假设List<? super T>能添加元素,那么同样需要满足添加的任意元素能够直接转化成T的任何一个超类。此时添加T的子类元素就能满足该要求,因为T的任意子类可以向上转型成T的任何超类。

List<? extends T>

List<? extends T>是被设计用来读取数据的泛型,并且只能读取类型为T的元素。原因如下:

元素是可以进行向上转型的,因此,我们可以这样做来读取元素

   List<? extends Number> list = new ArrayList<>(); 
   Number number=list.get(0);

可以读取,但不能写入,比如以下的代码就直接报错

public class Main {
 
    static class A { }
 
    static class B extends A { }
 
    static class C extends A { }
 
    public static void main(String[] args) {
        List<? extends A> list = new ArrayList<>();
        list.add(new A());//编译报错
        list.add(new B());//编译报错
        list.add(new C());//编译报错
    }
}

A的子类B与子类C是不能相互转换的,因此是不能往该list中添加元素

虽然不能添加元素,但可以在初始化的时候,接受一个已经定义好的list,而该list存放的类型一定相同,因此,List<? extends T>可直接接受一个定义好的list

    public static List<Integer> getList(){
        List<Integer> list=new ArrayList<>();
        list.add(1);
        return list;
    }
 
....
 
    public static void main(String[] args) {
        List<? extends Number> list = new ArrayList<>();
        list=getList();
    }

List<? super T>

List<? super T>是被设计用来添加数据的泛型,并且只能添加T类型或其子类类型的元素。

为什么只能是T类型及其子类型元素,超类类型的元素不可以吗?

超类类型转化为T类型,是需要强制类型转换的,是容易出现异常的,无法保障的。

而传入T类型及其子类类型时,能够直接转化为T的任意超类类型。比如,下面的代码是可以运行的

public class Main {
 
    static class A { }
 
    static class B extends A { }
 
    static class C extends A { }
 
    public static void main(String[] args) {
        List<? super A> list = new ArrayList<>();
        list.add(new A());
        list.add(new B());
        list.add(new C());
    }
 
}

该list也可以读取其中的元素,从第二节可以得出,只能用Object接收,没多大意义

   List<? super Integer> list2 = new ArrayList<>();
   list2.add(new Integer(1));
   Object integer=list2.get(0);

如果我们使用Object类型来接收获取到的元素,那么元素本身的类型就会丢失,因此,我们不使用List<? super T>来获取元素。

如果我们非要使用List<? super Integer>中的Integer类型来接收获取到的元素,那么必须进行强制类型转换,是会出现异常的,无法保障。

   List<? super Integer> list2 = new ArrayList<>();
   list2.add(new Integer(1));
   Integer integer1= (Integer) list2.get(0);

更加通俗易懂的例子

前言:向上转型是安全的,向下转型是不安全的,除非你知道List中的真实类型,否则向下转型就会报错

extends

List<? extends Number> foo3意味着下面的赋值语句都是合法的

List<? extends Number> foo3 = new ArrayList<Number>();  // Number "extends" Number (in this context)
List<? extends Number> foo3 = new ArrayList<Integer>(); // Integer extends Number
List<? extends Number> foo3 = new ArrayList<Double>();  // Double extends Number

1.读取

给定上述可能的赋值语句,能保证你从List foo3中取出什么样类型的对象?

  • 你可以读取一个Number对象,因为上面任意一个list都包含Number对象或者Number子类的对象(上面的Number、Integer、Double都可以转型成Number,并且是安全的,所以读取总是可以的)。如下代码就不会报错
      List<? extends Number> foo4 = new ArrayList<Integer>();
      Number number = foo4.get(0);
  • 你不能读取一个Integer对象,因为foo3可能指向的是List(与其运行时发现Double转成Integer报错,不如编译时就不让从foo3中取Integer对象)。如下代码编译时会报Incompatible types错的
      List<? extends Number> foo4 = new ArrayList<Integer>();
      Integer number = foo4.get(0);

因为编译的时候编译器只知道foo4引用是一个List<? extends Number>,要到运行时才会绑定到new ArrayList(),所以编译的时候是无法判断foo4指向的List中到底是什么类型,唯一能确定的就是这个类型是Number的子类(或者就是Number类)

  • 你也不能读取一个Double对象,因为foo3可能指向的是List

2.写入

给定上述可能的赋值语句,你能往List foo3中添加什么类型的对象从而保证它对于所有可能的ArrayList都是合法的呢?

  • 你不能添加一个Integer对象,因为foo3可能指向的是List。如下代码是会编译报错的
      List<? extends Number> foo4 = new ArrayList<Integer>();
      foo4.add(new Integer(1));

因为编译期间是无法知道foo4指向的ArrayList中到底放的是什么类型,只有到运行时才知道(就是Java所谓的晚绑定或运行时绑定)。与其到运行时发现往一个ArrayList中add一个Integer导致抛出类型转换异常,倒不如编译时就报错,即使ArrayList中放的就是Integer类型

  • 你不能添加一个Double对象,因为foo3可能指向的是List
  • 你不能添加一个Number对象,因为foo3可能指向的是List

总结一下:你不能往List<? extends T>中添加任何对象,因为你不能保证List真正指向哪个类型,所以不能确定添加的对象就是List所能接受的类型。能保证的,仅仅是你可以从List中读取的时候,你获得的肯定是一个T类型的对象(即使是T类型的子类对象也是T类型的)

super

List<? super Integer> foo3意味着下面任何一个赋值语句都是合法的

List<? super Integer> foo3 = new ArrayList<Integer>();  // Integer is a "superclass" of Integer (in this context)
List<? super Integer> foo3 = new ArrayList<Number>();   // Number is a superclass of Integer
List<? super Integer> foo3 = new ArrayList<Object>();   // Object is a superclass of Integer

1.读取

给定上述可能的赋值语句,当读取List foo3中的元素的时候,你能保证接收到什么类型的对象呢?

  • 你不能保证是一个Integer对象,因为foo3可能指向一个List或者List。
  • 你不能保证是一个Number对象,因为foo3可能指向一个List。
  • 你能保证的仅仅是它一定是一个Object类的实例或者Object子类的实例(但是你不知道到底是哪个子类)。
  • 2.写入

    给定上述可能的赋值语句,你能往List foo3中添加什么类型的对象从而保证它对于所有可能的ArrayList都是合法的呢?

    • 你可以添加一个Integer实例,因为Integer类型对于上述所有的list都是合法的。
    • 你可以添加任何Integer子类的实例,因为一个Integer子类的实例都可以向上转型成上面列表中的元素类型。
    • 你不可以添加Double类型,因为foo3可能指向的是ArrayList
    • 你不可以添加Number类型,因为foo3可能指向的是ArrayList
    • 你不可以添加Object类型,因为foo3可能指向的是ArrayList

    总结

    (1)List<? extends T>适用于读取数据,读取出来的数据全部用T类型接收。如果我们往此list中添加T类型不同的子类的话,各种子类无法相互转换,因此不能添加元素,但可接受初始赋值。

    (2)List<? super T>适用于添加元素,只能添加T类型或其子类类型。因为这些类型都能转换为T的任意超类类型(向上转型),因此我们可以对此list添加元素。只能用Object类型来接收获取到的元素,但是这些元素原本的类型会丢失。

致谢:https://blog.csdn.net/qq_33591903/article/details/82746794

号外!号外!

如果这篇文章对你有所帮助,或者有所启发的话,帮忙点赞、在看、转发、收藏,你的支持就是我坚持下去的最大动力!

List<? extends T>与List<? super T>的区别

扔掉工具类!MyBatis 一个简单配置搞定加密、解密


面试官:MQ 消息丢失、重复、积压问题,如何解决?


如何监听 MySQL 实现数据变化后的实时通知

List<? extends T>与List<? super T>的区别


原文始发于微信公众号(一安未来):List<? extends T>与List<? super T>的区别

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/44686.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!