Java 泛型是 Java 语言中强大的特性之一,它允许我们在编译时定义类型安全的集合和类。然而,泛型的类型系统不仅仅是简单的类型参数化,还涉及到更复杂的概念,如逆变(Contravariance)、协变(Covariance)和不可变性(Invariance)。这些概念在泛型的使用中至关重要,尤其是在处理继承关系和类型安全时。本文将深入探讨这些概念,并通过示例帮助你更好地理解它们。
一、基本概念
1. 不可变性(Invariance)
不可变性是 Java 泛型的默认行为。它表示泛型类型之间没有继承关系,即使它们的类型参数之间存在继承关系。
示例:
List<String> stringList = new ArrayList<>();
List<Object> objectList = stringList; // 编译错误:类型不兼容
在上面的代码中,尽管 String
是 Object
的子类,但 List<String>
并不是 List<Object>
的子类。因此,这种赋值是不允许的。
2. 协变(Covariance)
协变表示泛型类型之间的继承关系与类型参数的继承关系一致。换句话说,如果 A
是 B
的子类,那么 Generic<A>
也是 Generic<B>
的子类。
示例:
Java 中的数组是协变的:
String[] strings = new String[]{"Hello", "World"};
Object[] objects = strings; // 合法,因为数组是协变的
objects[0] = 42; // 运行时异常:ArrayStoreException
尽管这种赋值在编译时是合法的,但在运行时可能会导致 ArrayStoreException
,因为 objects
实际上引用的是一个 String[]
,不能存储 Integer
类型的值。
3. 逆变(Contravariance)
逆变表示泛型类型之间的继承关系与类型参数的继承关系相反。如果 A
是 B
的子类,那么 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>
,因为 Object
是 String
的父类。
二、协变与逆变的实际应用
1. 协变的应用:读取数据
协变通常用于从泛型集合中读取数据。例如,List<? extends Number>
表示一个可以包含 Number
或其子类(如 Integer
、Double
)的列表。
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
或其父类(如 Number
、Object
)的列表。
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 泛型的这些高级特性!