Java中的泛型有两个重要特性:不可变性(invariance)和类型擦除(type erasure)。这两个概念在理解和使用泛型时非常重要。以下是对这两个特性的详细解释:
不可变性(Invariance)
在Java泛型中,不可变性意味着泛型类型是严格的,不会自动适应其子类或父类。举个例子:
java
List<Number> numberList = new ArrayList<>();
List<Integer> integerList = new ArrayList<>();
numberList = integerList; // 编译错误
上面的代码中,即使Integer
是Number
的子类,但是List<Integer>
并不是List<Number>
的子类型。这就是不可变性。你不能将List<Integer>
赋值给List<Number>
,因为泛型类型是不变的。
为了解决这种问题,Java提供了通配符来处理类型的协变和逆变:
java
List<? extends Number> numberList = new ArrayList<>();
List<Integer> integerList = new ArrayList<>();
numberList = integerList; // 允许
使用? extends Number
表示可以赋值给任何Number
的子类的列表。类似地,? super Integer
表示任何Integer
的父类的列表。
类型擦除(Type Erasure)
类型擦除是指在编译过程中,Java编译器会移除所有的泛型类型信息。这意味着在运行时,泛型类型的信息是不可用的。
例如:
java
List<String> stringList = new ArrayList<>();
stringList.add("hello");
String str = stringList.get(0);
在编译时,stringList
的类型是List<String>
,但是在运行时,类型信息被擦除,stringList
变成了List
。编译器在编译时插入了类型转换代码,以确保类型安全:
java
String str = (String) stringList.get(0);
类型擦除带来了一些限制,比如你不能在运行时获取泛型类型信息,也不能创建泛型类型的实例或数组:
java
List<String>[] listArray = new List<String>[10]; // 编译错误
理解这两个概念对于正确使用Java泛型非常重要。不可变性确保了类型安全性,而类型擦除则是Java泛型实现的一部分,确保与Java旧版本的兼容性。
Java泛型中的逆变(contravariance)指的是允许使用某个泛型类型的超类型来替代该泛型类型。逆变通过通配符 ? super T
实现,表示接受 T
类型或 T
类型的任何超类型。这个概念主要用于写操作,确保我们可以向集合中添加元素。
逆变的示例
假设我们有以下类层次结构:
java
class Animal { }
class Dog extends Animal { }
class Cat extends Animal { }
我们定义一个逆变的集合:
java
List<? super Dog> animals = new ArrayList<Animal>();
这个声明表示 animals
可以是 Dog
的任何超类型的列表,例如 List<Animal>
或 List<Object>
。
使用逆变的场景
逆变主要用于写入的场景,因为你可以确保向集合中添加的元素是 Dog
类型或其子类型。例如:
java
animals.add(new Dog()); // 允许
animals.add(new Puppy()); // 允许,假设 Puppy extends Dog
但是,不能从逆变的集合中读取特定类型的元素,因为编译器无法确定从集合中读取的元素的具体类型,只能确定是某种类型的 Object
。
java
Dog dog = animals.get(0); // 编译错误
Object obj = animals.get(0); // 允许
示例:逆变接口
假设我们设计一个处理动物的接口:
java
interface AnimalHandler<T> {
void handle(T animal);
}
我们可以实现这个接口:
java
class DogHandler implements AnimalHandler<Dog> {
@Override
public void handle(Dog dog) {
System.out.println("Handling a dog");
}
}
利用逆变,我们可以定义一个方法,接受任何类型的 AnimalHandler
:
java
public class Zoo {
public static void processAnimal(AnimalHandler<? super Dog> handler, Dog dog) {
handler.handle(dog);
}
public static void main(String[] args) {
DogHandler dogHandler = new DogHandler();
Dog dog = new Dog();
processAnimal(dogHandler, dog); // Handling a dog
}
}
在这个示例中,processAnimal
方法接受一个 AnimalHandler<? super Dog>
,意味着我们可以传入处理 Dog
的任何超类型的处理器,例如 AnimalHandler<Animal>
或 AnimalHandler<Object>
。
总结
逆变(? super T
)使得我们可以更加灵活地处理泛型集合,特别是在写入操作中。通过使用 ? super T
,我们可以向集合中添加 T
类型或其子类型的元素,同时保持类型安全。
理解逆变可以帮助我们设计更灵活和通用的接口和方法,特别是在处理继承层次结构中的类型时。