java 中协变,逆变,不变简单理解

共 4402字,需浏览 9分钟

 ·

2023-08-23 14:44

1. 什么是协变、逆变、不变

假设有两个类,Dog和Animal,如果用Dog <= Animal 表示它俩的继承关系。用f(type) 表示类型构造器,一个已知的类型被类型构造器处理后就是一个崭新的类型。


协变就是f(Dog)是f(Animal)的子类,即f(Dog) <= f(Animal);逆变就是f(Animal)是f(Dog)的子类,即f(Aniaml) <= f(Dog);不变就是指f(Dog)与f(Animal)之间没有关系


型构造器可以是泛型 List<Animal> , 可以是数组  Animal[] ,可以是函数方法  method(Animal)

2. java 泛型和数组

  • • java泛型不支持逆变和协变,只能是不变

     List<Animal> animals = new ArrayList<Dog>(); // 编译错误,java泛型不支持逆变和协变,只能是不变
  
  • • java数组支持协变,不支持逆变,但也正因为支持协变,数组可能踩坑

     Animal[] animals = new Dog[10];
 animals[0] = new Dog();
 animals[1] = new Cat(); // 运行时异常 java.lang.ArrayStoreException

3. java 泛型协变&上界

  • • 考虑泛型支持协变后有什么好处,解决了什么问题?

        // processAnimals方法中如果不支持泛型协变,那么难道要通过接收类型的不同重写好多的方法吗?太麻烦了!!!
    public static void main(String[] args) {
    processAnimals(new ArrayList<Cat>()); // 编译错误
    proceessAimals(new ArrayList<Dog>()); // 编译错误
    }
     public static void processAnimals(List<Animal> animals) {
    // ...
    }
  • • 在java泛型中加入extends关键字实现支持协变,<? extends Animal> 其中?代表不确定类型通配符,和extends结合就声明了泛型的上限,表示接收的类型只能是指定类型或是该类型的子类

  • • 上面说数组支持协变,添加其他类型会出现运行时异常,泛型协变为了杜绝这种隐患,所以泛型协变除了null 可以写,其他的都不能写(编译异常),可以读

  • • 指定上界的好处,限定类型(编译错误提醒);可以访问上界类型中的方法(要不只能访问Object类中方法)

        // 使用extends关键字让泛型支持协变,这样processAniamls方法中的泛型变量就能接收子类集合了
    public static void processAnimals(List<> extends Animal> anumals){
    
    }
    
    public static void processAnimalsExtends(List<? extends Animal> animals) {
        animals.add(null); // 正常
        /**
         * 协变不允许传入除null
         */

        // animals.add(new Animal()); // 编译错误
        // animals.add(new Dog()); // 编译错误
        animals.remove(new Dog()); // 不会破坏类型安全
        animals.contains(new Dog()); // 不会破坏类型安全
    }

    public class AKA<T extends Animal>{

        public static void main(String[] args) {
        AKA<Animal> animalAKA = new AKA<>();
        AKA<Cat> catAKA = new AKA<>();
        AKA<String> stringAKA = new AKA<String>();// 编译错误
    }
}

4.java 泛型逆变&下界

  • • 使用super关键字声明泛型下界,如<? super Dog>,逆变后就可以接收本类型或父类型的泛型类

  • • 逆变可以添加元素,逆变泛型可以接受本类型及父类型元素,但是添加元素只能添加指定类型或指定类型的子类

        public static void testDog(List<? super Dog> dest,list<? extends Dog> src){
        for(Dog dog :src){
        if(dog.isHappy()){
         dest.add(dog);
        }
        }
    }
    
    public static void main(Stringp[] args){
    testDog(new ArrayList<Animal>(),new ArrayList<哈士奇>()); // 正确
    }

5.应用场景

  • • 只读不写:用协变

  • • 只写不读:用逆变

  • • 又读又写:用不变

浏览 18
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报