Java泛型之协变与逆变

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

不可变性(Invariance)

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

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

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

为了解决这种问题,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<String>,但是在运行时,类型信息被擦除,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 的任何超类型的列表,例如 List<Animal>List<Object>

使用逆变的场景

逆变主要用于写入的场景,因为你可以确保向集合中添加的元素是 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<? super Dog>,意味着我们可以传入处理 Dog 的任何超类型的处理器,例如 AnimalHandler<Animal>AnimalHandler<Object>

总结

逆变(? super T)使得我们可以更加灵活地处理泛型集合,特别是在写入操作中。通过使用 ? super T,我们可以向集合中添加 T 类型或其子类型的元素,同时保持类型安全。

理解逆变可以帮助我们设计更灵活和通用的接口和方法,特别是在处理继承层次结构中的类型时。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java泛型中的协变和逆变都是针对类型转换的规定。 协变(covariant):指的是继承链中子类(派生类)类型能够作为父类(基类)类型的一种属性,也就是子类可以作为父类使用的能力。在泛型中,协变的概念可以用来表示如果类型A是类型B的一个子类型,那么泛型类G<A>就可以视作泛型类G<B>的一个子类型。 例子: ```java // Animal类 public class Animal {} // Dog类是Animal类的子类 public class Dog extends Animal {} // 泛型接口List public interface List<E> { void add(E e); E get(int index); } // 定义一个方法acceptList,其形参类型为List<? extends Animal> public static void acceptList(List<? extends Animal> list) { for (Animal animal : list) { // ... } } // List类型为List<Dog> List<Dog> list = new ArrayList<Dog>(); list.add(new Dog()); acceptList(list); // 在这里,我们可以传入一个List<Dog>参数,因为Dog类是Animal类的子类 ``` 逆变(contravariant):指的是继承链中父类(基类)类型能够作为子类(派生类)类型的一种属性,也就是父类可以作为子类使用的能力。在泛型中,逆变的概念可以用来表示如果类型A是类型B的一个超类型,那么泛型类G<B>就可以视作泛型类G<A>的一个子类型。 例子: ```java // Animal类 public class Animal {} // Dog类是Animal类的子类 public class Dog extends Animal {} // 泛型接口Comparator public interface Comparator<T> { int compare(T o1, T o2); } // 定义一个方法sortList,其形参类型为List<? super Dog> public static void sortList(List<? super Dog> list) { // ... } // List类型为List<Animal> List<Animal> list = new ArrayList<Animal>(); list.add(new Animal()); sortList(list); // 在这里,我们可以传入一个List<Animal>参数,因为Animal类是Dog类的超类型 ``` extends和super关键字常常用于定义泛型类型参数的上边界(upper bound)和下边界(lower bound)。extends表示类型参数的上限,超过这个范围就会导致编译错误;super表示类型参数的下限,超过这个范围也会导致编译错误。 例子: ```java // 泛型类Pair,其类型参数T有上限(用extends)为Comparable<? super T>,表示类型T要么是Comparable<? super T>本身,要么是Comparable<? super T>的子类型 public class Pair<T extends Comparable<? super T>> { private T first; private T second; public Pair(T first, T second) { this.first = first; this.second = second; } public T getFirst() { return first; } public T getSecond() { return second; } public T max() { return first.compareTo(second) >= 0 ? first : second; } } // Pair类型为Pair<String> Pair<String> pair = new Pair<String>("hello", "world"); String max = pair.max(); // 在这里,我们可以调用max方法,因为String类实现了Comparable<String>接口 ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

魔道不误砍柴功

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值