Java泛型之协变与逆变

Java中的泛型有两个重要特性:不可变性(invariance)和类型擦除(type erasure)。这两个概念在理解和使用泛型时非常重要。以下是对这两个特性的详细解释:

不可变性(Invariance)

在Java泛型中,不可变性意味着泛型类型是严格的,不会自动适应其子类或父类。举个例子:

List<Number> numberList = new ArrayList<>();
List<Integer> integerList = new ArrayList<>();
numberList = integerList; // 编译错误

上面的代码中,即使IntegerNumber的子类,但是List并不是List的子类型。这就是不可变性。你不能将List赋值给List,因为泛型类型是不变的。

为了解决这种问题,Java提供了通配符来处理类型的协变和逆变:

List<? extends Number> numberList = new ArrayList<>();
List<Integer> integerList = new ArrayList<>();
numberList = integerList; // 允许

使用? extends Number表示可以赋值给任何Number的子类的列表。类似地,? super Integer表示任何Integer的父类的列表。

类型擦除(Type Erasure)

类型擦除是指在编译过程中,Java编译器会移除所有的泛型类型信息。这意味着在运行时,泛型类型的信息是不可用的。

例如:

List<String> stringList = new ArrayList<>();
stringList.add("hello");
String str = stringList.get(0);

在编译时,stringList的类型是List,但是在运行时,类型信息被擦除,stringList变成了List。编译器在编译时插入了类型转换代码,以确保类型安全:

String str = (String) stringList.get(0);

类型擦除带来了一些限制,比如你不能在运行时获取泛型类型信息,也不能创建泛型类型的实例或数组:

List<String>[] listArray = new List<String>[10]; // 编译错误

理解这两个概念对于正确使用Java泛型非常重要。不可变性确保了类型安全性,而类型擦除则是Java泛型实现的一部分,确保与Java旧版本的兼容性。

Java泛型中的逆变(contravariance)指的是允许使用某个泛型类型的超类型来替代该泛型类型。逆变通过通配符 ? super T 实现,表示接受 T 类型或 T 类型的任何超类型。这个概念主要用于写操作,确保我们可以向集合中添加元素。

逆变的示例

假设我们有以下类层次结构:

class Animal { }
class Dog extends Animal { }
class Cat extends Animal { }

我们定义一个逆变的集合:

List<? super Dog> animals = new ArrayList<Animal>();

这个声明表示 animals 可以是 Dog 的任何超类型的列表,例如 ListList

使用逆变的场景

逆变主要用于写入的场景,因为你可以确保向集合中添加的元素是 Dog 类型或其子类型。例如:

animals.add(new Dog()); // 允许
animals.add(new Puppy()); // 允许,假设 Puppy extends Dog

但是,不能从逆变的集合中读取特定类型的元素,因为编译器无法确定从集合中读取的元素的具体类型,只能确定是某种类型的 Object

Dog dog = animals.get(0); // 编译错误
Object obj = animals.get(0); // 允许

示例:逆变接口

假设我们设计一个处理动物的接口:

interface AnimalHandler<T> {
    void handle(T animal);
}

我们可以实现这个接口:

class DogHandler implements AnimalHandler<Dog> {
    @Override
    public void handle(Dog dog) {
        System.out.println("Handling a dog");
    }
}

利用逆变,我们可以定义一个方法,接受任何类型的 AnimalHandler

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,意味着我们可以传入处理 Dog 的任何超类型的处理器,例如 AnimalHandlerAnimalHandler

总结

逆变(? super T)为我们处理泛型集合提供了更大的灵活性,尤其是在进行写入操作时。在 Java 的泛型机制中,使用逆变可以让我们向集合中添加类型为 T 或 T 的子类型的元素,同时保持类型的安全性。

具体来说,逆变的作用是允许我们将一个泛型对象限制为某个类型 T 的超类型,这意味着我们可以对集合执行的操作是"写入",而不是"读取"。由于编译器知道该集合至少能容纳类型 T 的对象及其子类型,因此可以安全地向集合中插入 T 类型的元素。而这种灵活性在泛型设计中尤其有用,因为它允许代码在处理继承层次结构时适应不同层级的类型,而不需要过多地关注具体类型。

你可能感兴趣的:(java,python,开发语言)