积跬步至千里

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.应用场景

只读不写:用协变
只写不读:用逆变
又读又写:用不变

posted @ 2023-07-05 14:24  大阿张  阅读(232)  评论(0编辑  收藏  举报