Java泛型的协变与逆变

本文深入探讨了Java中泛型的协变和逆变特性,通过实例解释了为什么数组是协变的,而泛型是不变的。文章介绍了如何使用extends和super关键字来实现消费场景的协变和生产场景的逆变,并分析了Collection接口中相关方法的源码,以说明这些特性的实际应用。最后,强调了解这些知识对于提高代码质量和安全性的重要性。
摘要由CSDN通过智能技术生成

从面向对象说起
Java作为一门面相对象的语言,当然是支持面相对象的三大基本特性的,反手就蹦出三个词:封装、继承、多态。

我们假设有三个类,动物、猫、狗。父类是动物Animal,有两个子类猫Cat和狗Dog。

那在Java中或其它任何支持面相对象的语言中,子类可以把引用赋值给父类。下面这段代码没有任何问题:

Animal animalOne = new Cat();
Animal animalTwo = new Dog();
1
理论上来说,一只猫是一只动物,一只狗也是一只动物,所以这完全是可以理解的。其实,这也是SOLID原则中的“里氏替换原则”的一种体现。

数组的协变
如果一只猫是一只动物,那一群猫是一群动物吗?一群狗是一群动物吗?Java数组认为是的。于是你可以这样写:

Animal[] animals = new Cat[2];
1
这看起来也没有什么问题。但既然都是一群动物了,我往这一群动物中添加一只猫、一只狗,它还是一群动物,这应该是合理的对吧?来看看这段代码:

Animal[] animals = new Cat[2];
animals[0] = new Cat();
// 下面这行代码会抛运行时异常 animals[1] = new Dog();
Animal animal = animal[0];
1
很好,编译没有任何问题。但是一运行,会抛出一个运行时异常:ArrayStoreException。这个异常头顶的注释已经写得很明显了,如果你往数组中添加一个类型不对的对象,就会抛这个异常。它是从JDK 1.0就存在的一个异常。

这么一想,对啊,animals虽然门面上是一个Animal数组,但是它运行时的本质还是一个Cat数组啊,一个Cat数组怎么能添加一个Dog呢?但Java编译器并没有这么智能,而且上述代码在编

译器看来也是合理合法的,所以也就让它编译过了。

所以这种情况,编译器100%过,而运行时100%抛异常,这不是大写的BUG是啥?

如果Cat是Animal的子类型,那么Cat[]也是Animal[]的子类型,我们称这种性质为协变(covariance)。Java中,数组是协变的。

泛型的不变性
在Java 1.5之前,是没有泛型的。那个时候从集合中存取对象都是Object类型,所以每次取出对象后必须进行强转:

List list = new LinkedList(); list.add(123); list.add(“123”); int a = (int)list.get(0); // 下面这段代码会在运行时抛异常 int b = (int)list.get(1); 复制代码
1
如果不小心存入集合中对象类型是错的,会在运行时报强转异常。而1.5提供泛型以后,可以让编译器自动帮助转换,并对代码进行检查,使程序更加安全。

在Java8又加入了泛型的类型推导功能,使用泛型以后,我们的代码看起来变得简洁又安全了:

List list = new LinkedList<>();
list.add(123);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值