java 中协变,逆变,不变简单理解
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.应用场景
-  • 只读不写:用协变 
-  • 只写不读:用逆变 
-  • 又读又写:用不变 
评论
