专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

Java 泛型协变逆变深入解析:理解 ? extends 和 ? super 的正确姿势

Java 泛型是 Java 语言中强大的特性之一,它允许我们在编译时定义类型安全的集合和类。然而,泛型的类型系统不仅仅是简单的类型参数化,还涉及到更复杂的概念,如逆变(Contravariance)协变(Covariance)和不可变性(Invariance)。这些概念在泛型的使用中至关重要,尤其是在处理继承关系和类型安全时。本文将深入探讨这些概念,并通过示例帮助你更好地理解它们。

一、基本概念

1. 不可变性(Invariance)

不可变性是 Java 泛型的默认行为。它表示泛型类型之间没有继承关系,即使它们的类型参数之间存在继承关系。

示例:

List<String> stringList = new ArrayList<>();
List<Object> objectList = stringList; // 编译错误:类型不兼容

在上面的代码中,尽管 StringObject 的子类,但 List<String> 并不是 List<Object> 的子类。因此,这种赋值是不允许的。

2. 协变(Covariance)

协变表示泛型类型之间的继承关系与类型参数的继承关系一致。换句话说,如果 AB 的子类,那么 Generic<A> 也是 Generic<B> 的子类。

示例:

Java 中的数组是协变的:

String[] strings = new String[]{"Hello", "World"};
Object[] objects = strings; // 合法,因为数组是协变的
objects[0] = 42; // 运行时异常:ArrayStoreException

尽管这种赋值在编译时是合法的,但在运行时可能会导致 ArrayStoreException,因为 objects 实际上引用的是一个 String[],不能存储 Integer 类型的值。

3. 逆变(Contravariance)

逆变表示泛型类型之间的继承关系与类型参数的继承关系相反。如果 AB 的子类,那么 Generic<B>Generic<A> 的子类。

示例:

Java 中的泛型通配符 ? super T 支持逆变:

List<Object> objectList = new ArrayList<>();
List<? super String> stringList = objectList; // 合法,因为 ? super String 支持逆变
stringList.add("Hello"); // 合法

在这个例子中,List<? super String> 可以接受 List<Object>,因为 ObjectString 的父类。

二、协变与逆变的实际应用

1. 协变的应用:读取数据

协变通常用于从泛型集合中读取数据。例如,List<? extends Number> 表示一个可以包含 Number 或其子类(如 IntegerDouble)的列表。

List<Integer> integerList = Arrays.asList(1, 2, 3);
List<? extends Number> numberList = integerList; // 协变

Number firstNumber = numberList.get(0); // 合法,可以安全读取
// numberList.add(42); // 编译错误:无法添加元素

在这个例子中,numberList 可以安全地读取元素,但不能添加元素,因为编译器无法确定具体的类型。

2. 逆变的应用:写入数据

逆变通常用于向泛型集合中写入数据。例如,List<? super Integer> 表示一个可以包含 Integer 或其父类(如 NumberObject)的列表。

List<Object> objectList = new ArrayList<>();
List<? super Integer> integerList = objectList; // 逆变

integerList.add(42); // 合法,可以安全添加
// Integer value = integerList.get(0); // 编译错误:无法安全读取

在这个例子中,integerList 可以安全地添加 Integer 类型的元素,但不能安全地读取元素,因为编译器无法确定具体的类型。

三、不可变性的意义

不可变性是 Java 泛型的默认行为,它确保了类型安全。虽然不可变性限制了泛型类型之间的继承关系,但它避免了潜在的运行时错误。

示例:

List<String> stringList = new ArrayList<>();
List<Object> objectList = stringList; // 编译错误:类型不兼容

如果Java 泛型是协变的,那么 objectList 可以引用 stringList,但这会导致类型安全问题:

objectList.add(42); // 如果允许,这将破坏 stringList 的类型安全
String value = stringList.get(0); // 运行时异常:ClassCastException

通过保持不可变性,Java 泛型确保了类型安全,避免了这种潜在的问题。

四、总结

不可变性(Invariance) :泛型类型之间没有继承关系,即使它们的类型参数之间存在继承关系。这是 Java 泛型的默认行为,确保了类型安全。

协变(Covariance) :泛型类型之间的继承关系与类型参数的继承关系一致。通常用于读取数据,例如 List<? extends Number>

逆变(Contravariance) :泛型类型之间的继承关系与类型参数的继承关系相反。通常用于写入数据,例如 List<? super Integer>

理解这些概念对于编写类型安全且灵活的泛型代码至关重要。通过合理使用协变和逆变,我们可以在保证类型安全的同时,提高代码的灵活性和可复用性。希望本文能帮助你更好地掌握 Java 泛型的这些高级特性!

未经允许不得转载:搜云库 » Java 泛型协变逆变深入解析:理解 ? extends 和 ? super 的正确姿势

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们