网站首页 > 精选教程 正文
前言
“泛型已经够烧脑了,啥还来个逆变和协变?别啊,我放弃!”
别慌,今天我们用最通俗易懂的方式,告诉你 逆变 和 协变 是什么、怎么用,以及它们出没的常见场景。看完保证你信心满满,直呼 “啊,原来这么简单!”
一、啥是逆变和协变?(搞清概念,打好地基)
要理解逆变和协变,先看个问题:
你有一群动物,Dog 和 Cat 都是 Animal 的子类。如果你有一个 List<Animal>,你觉得能装 Dog 和 Cat 的 List 吗?
很抱歉,Java 的泛型系统会告诉你:不行!
List<Dog> dogList = new ArrayList<>();
List<Animal> animalList = dogList; // 报错!不兼容。
这是因为,泛型不支持子类型替换父类型,也就是所谓的 类型不变性。
那么问题来了,我就是想灵活一点,怎么办? 这时,Java 泛型提供了两种“变通方式”:
- 协变(? extends T):向下兼容——"能看不能改" 容器里的类型可以是 T 或 T 的子类,但你不能往里加东西(除了 null)。
- 逆变(? super T):向上兼容——"能改不能看" 容器里的类型可以是 T 或 T 的父类,但你只能保证添加操作安全。
二、协变和逆变的代码演示
1. 协变(? extends T):容器中的元素,只能“读”不能“写”
例子:定义一个方法,打印任何类型的列表内容:
public static void printList(List<? extends Animal> list) {
for (Animal animal : list) {
System.out.println(animal);
}
}
调用:
List<Dog> dogs = List.of(new Dog("旺财"), new Dog("小黑"));
List<Cat> cats = List.of(new Cat("咪咪"), new Cat("花花"));
printList(dogs); // 输出:旺财,小黑
printList(cats); // 输出:咪咪,花花
要点:
- ? extends T 表示列表中的元素是 T 或 T 的子类。
- 只能读取列表中的内容,不能往里添加(除了 null)。
list.add(new Dog()); // 报错:禁止添加!
list.add(null); // 允许,但慎用。
场景适用:
- 只读操作:当你只需要“遍历”和“读取”列表中的元素时,用协变。
2. 逆变(? super T):容器中的元素,只能“写”不能“读”
例子:定义一个方法,往列表中添加某种类型的动物:
public static void addAnimals(List<? super Dog> list) {
list.add(new Dog("小黄"));
list.add(new Dog("大黄"));
}
调用:
List<Animal> animals = new ArrayList<>();
addAnimals(animals); // 成功添加两只狗
List<Object> objects = new ArrayList<>();
addAnimals(objects); // 成功,因为 Object 是 Animal 的父类
要点:
- ? super T 表示列表中的元素是 T 或 T 的父类。
- 可以安全地往列表中添加 T 或其子类,但不能保证读取出来的类型。
Object obj = list.get(0); // 只能当作 Object 处理
Dog dog = list.get(0); // 报错:无法确认是 Dog!
场景适用:
- 只写操作:当你只需要“添加”特定类型的元素时,用逆变。
三、协变和逆变的终极场景(大显身手)
1. 协变场景:动物园的饲养员(只看不动手)
饲养员想知道有哪些动物,但不负责加新动物:
public static void showAnimals(List<? extends Animal> animals) {
for (Animal animal : animals) {
System.out.println("发现了:" + animal);
}
}
调用:
List<Dog> dogs = List.of(new Dog("大白"), new Dog("二哈"));
showAnimals(dogs);
2. 逆变场景:动物园的管理员(只加不查看)
管理员要把动物关进笼子,但不关心笼子里原来有什么:
public static void cageAnimals(List<? super Dog> cages) {
cages.add(new Dog("哈士奇"));
cages.add(new Dog("柯基"));
}
调用:
List<Animal> cages = new ArrayList<>();
cageAnimals(cages);
3. 协变 + 逆变场景:大棚收割机(双管齐下!)
既要读取,又要添加,怎么办?分开两步来解决!
// 读取
List<? extends Fruit> fruits = new ArrayList<>();
Fruit fruit = fruits.get(0); // 可以读
// 添加
List<? super Apple> apples = new ArrayList<>();
apples.add(new Apple()); // 可以写
四、终极口诀(方便记忆!)
协变 extends:只能“读”,不能“写”;上限是 T 和 T 的子类。 逆变 super:只能“写”,不能“读”;下限是 T 和 T 的父类。
记住一句话: “能读就别写,能写就别读,想全能,那是不可能!”
五、总结
协变和逆变,是 Java 泛型中非常强大的工具,尤其适合应对复杂的类型安全问题。
- 协变:适用于只读场景,比如迭代器、打印列表。
- 逆变:适用于只写场景,比如批量添加数据。
掌握了它们,你就能在写代码时游刃有余,灵活应对千变万化的需求!
希望这篇文章能让你在协变与逆变的迷雾中拨云见日,写出让自己满意、同事膜拜的代码!
- 上一篇: Java 中 List 分片的 5 种方法
- 下一篇: 能懂系列之 List 集合详解
猜你喜欢
- 2024-12-07 JDK8新特性Stream流的filter方法根据条件过滤list数据「效率高」
- 2024-12-07 python打印字典内容
- 2024-12-07 张小飞的Java之路——第三十一章——List
- 2024-12-07 Java批量更新太慢?多线程+List分段完美解决
- 2024-12-07 java Collection集合删除某一元素方法汇总
- 2024-12-07 能懂系列之 List 集合详解
- 2024-12-07 Java 中 List 分片的 5 种方法
- 2024-12-07 Java动态字节技术之Javassist
- 2024-12-07 硬核!最全的延迟任务实现方式汇总!附代码(强烈推荐)
- 2024-12-07 一文掌握在Python列表中添加元素的多种方法
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- nginx反向代理 (57)
- nginx日志 (56)
- nginx限制ip访问 (62)
- mac安装nginx (55)
- java和mysql (59)
- java中final (62)
- win10安装java (72)
- java启动参数 (64)
- java链表反转 (64)
- 字符串反转java (72)
- java逻辑运算符 (59)
- java 请求url (65)
- java信号量 (57)
- java定义枚举 (59)
- java字符串压缩 (56)
- java中的反射 (59)
- java 三维数组 (55)
- java插入排序 (68)
- java线程的状态 (62)
- java异步调用 (55)
- java中的异常处理 (62)
- java锁机制 (54)
- java静态内部类 (55)
- java怎么添加图片 (60)
- java 权限框架 (55)
本文暂时没有评论,来添加一个吧(●'◡'●)