泛型的一些小疑点

本文深入解析Java中的List泛型概念,包括List<?>、List<T>、List<Object>的区别及<?extendsT>与<?superT>的使用场景。
摘要由CSDN通过智能技术生成

泛型程序设计

List<?>与List<T>、List<Object>、List

能说说他们之间的区别吗?!我在第一次看到这几个东西的时候也会摸不着头脑。。。甚至会将他们弄混!

  • 首先?是一个类型通配符,T可以看做是一个类型指代,Object是具体的泛型类型

    • List<?>表示有一个List,但是我不清楚里面元素是什么类型!
    • List则表示一个List,而其中的元素类型都是T类型(或者其子类型),并且这个T是可以唯一确定的!
    • List表示可以存放任何数据的List,因为Object是任何类型的父类!
  • **List<?>在经过赋值前,可以接收任何类型的集合赋值,但是一旦赋值后,就不能往集合中add值了!**但是允许清除和删除!

  • 再来看看List和List<Object>,很多人将它们俩混为一谈

    public class GenericTest01 {
        public static void main(String[] args) {
            List list = new ArrayList();
            list.add(Integer.valueOf(22));
            list.add("Hello");
            list.add(3.56);
    
            List<Object> objectList = list;
            objectList.add(Integer.valueOf(44));
            objectList.add("World");
            objectList.add(4.77);
    
            objectList.forEach(System.out::println);
        }
    }
    

    从这段代码运行结果来看确实两者好像没什么区别!但是遇到下面这种情况,就会出现问题

    List<Integer> integerList = list;
    

    这样写编译只是报警但是运行就会出错!

    因为List在没有写明泛型类型时候,是可以往里面任意存值的,但是无论怎么存都逃不出Object的手掌心!所以将他赋值给一个List声明不会出错,但是赋值给一个List,就相当于贸然认为List中都是Integer对象,当然如果确实都是Integer(或其子类)倒还好,如果不是,则会在类型转换的时候抛出ClassCastException异常

关于泛型约束<? extends T>与<? super T>

List最大的问题就是只能放置一种类型。如果需要放置多种受泛型约束的类型时就要用到<? extends T><? super T>。但是在使用时我们会误解其所代表的意思!并且两者有明确的适用场景。

我们现在定义三个类,根据继承关系依次为:AnimalCatGarfield

<? extends T>

**可以接收任何泛型为T或其子类的集合,即泛型上界为T。**我们很容易误解为它是一个存放T及其子类的集合

但是你在试图运行以下代码的时候,却会发现add操作是不允许的

List<? extends Cat> extendsCat = new ArrayList<>();
extendsCat.add(new Garfield());
extendsCat.add(new Cat());

因为<? extends T>指的是一个具有继承自T的类型的泛型列表。**即<? extends T>只是对List的泛型做一个限制和检查!要求List的泛型类型必须是T或者其子类!**所以你会看到这样的代码:

extendsCat = new ArrayList<Cat>();
extendsCat = new ArrayList<Garfield>();

// 不允许
extendsCat = new ArrayList<Animal>();
extendsCat = new ArrayList<Object>();

前面两行的赋值是没有问题的,但是后面两行则是不符合规范的!因为Animal、Object均不是继承自Cat的!

明白这个道理以后,再说说为什么一旦赋值后,集合就不能向泛型声明为<? extends T>的列表中添加元素了:

因为<? extends T>可以接受集合的泛型类型是不稳定的,但是都有统一的上界。但即便是上界统一,程序也不敢让你贸然向一个未知的泛型集合添加数据,一旦你添加的数据类型“高于”真实的泛型类型,那么类型转换就会抛出异常!

例如:

List<? extends Cat> extendsCat = new ArrayList<>();
extendsCat = new ArrayList<Cat>();
extendsCat = new ArrayList<Garfield>();

以上三种赋值都是可以的,但是如果真实代码中赋值语句是最后一条,那么真实泛型就是Garfield。

如果此时允许你向集合中添加数据,你能得知的就只是这个集合的泛型类型是继承自Cat的,如果你贸然add一个Cat对象,程序就会出现异常!!因为你无法确实你所添加的数据就一定是集合的真实类型或其子类!

但是null是可以添加的,因它可以表示任何对象。

然后我们再来看看取数据,既然我们清楚集合的泛型类型上限是T,那么无论集合的真实类型是什么,可以确保我们取出的数据必定是T或其子类,所以取出的数据的类型默认是T!

Cat obj = extendsCat.get(0);

所以说**<? extends T>的场景是put功能受限,是Get First,适用于消费集合元素为主的场景!**


<? super T>

与<? extends T>,刚好相反,它要求赋值的集合的泛型类型必须是T或其父类!即泛型的下界是T。

那么这些赋值语句的结果就会与刚才大不相同:

List<? super Cat> superCat = new ArrayList<>();
superCat = new ArrayList<Animal>();
superCat = new ArrayList<Object>();
superCat = new ArrayList<Cat>();
// 不允许
superCat = new ArrayList<Garfield>();

很明显最后一个赋值语句是不能通过的!因为Garfield并不是Cat的父类!

并且现在你会发现add可以使用了,但是只允许添加Cat或其子类对象!这是为什么?!(明明都说了泛型是T的父类,为什么反而只能添加T及其子类呢?!)

这就是我经常陷入的误解区域,<? super T>只能说明它所指向的集合的泛型类型是T的父类,但是不一定说是集合内的元素就都是Cat的父类!!

想要走出这个误区,我们得站在设计者的角度看:

<? super T>声明能够表示这个集合的泛型下界是Cat,但是无法确保它的上界。既然允许用户往里面添加数据,那么 在无法得知集合真实泛型的情况下,最保险的数据就是这个下界及其子类对象。这样就不会导致类型转换抛出异常!

例如:

List<? super Cat> superCat = new ArrayList<Cat>();
superCat.add(new Animal());

我们在添加数据的时候,如果添加了这个下界的父类型对象,就可能出现数据类型“高于”真实集合泛型的情况,进而导致类型转换失败抛出异常!

但是我们在取数据时,会发现一个有意思的现象。无论集合的真实泛型,我们通过<? super T>声明的集合取出的数据都是Object!如果你能懂前面“extends T取出的对象都是T”是怎么回事,理解这个也就是分分钟的事情。

因为通过<? super T>的声明我们是无法确定集合的真实泛型的,我们只清楚集合泛型的下限是T,但是对我们取数据没有任何价值,因为集合里面的数据可能是T的父类,也可能是T的子类!所以保险起见,因为所有的对象的共同祖先是Object,那么索性将取出的数据都视为Object,准不会出错!

解这个也就是分分钟的事情。

因为通过<? super T>的声明我们是无法确定集合的真实泛型的,我们只清楚集合泛型的下限是T,但是对我们取数据没有任何价值,因为集合里面的数据可能是T的父类,也可能是T的子类!所以保险起见,因为所有的对象的共同祖先是Object,那么索性将取出的数据都视为Object,准不会出错!

所以说**<? super T>的场景是get功能受限,是Put First,适用于以生产集合元素为主的场景!**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值