Java协变、逆变、类型擦除
阅读原文时间:2023年07月09日阅读:1

定义

Java中String类型是继承自Object的,姑且记做String ≦ Object,表示String是Object的子类型,String的对象可以赋给Object的对象。而Object的数组类型Object[],理解成是由Object构造出来的一种新的类型,可以认为是一种构造类型,记f(Object),那么可以这么来描述协变和逆变:

当A ≦ B时,如果有f(A) ≦ f(B),那么f叫做协变(子类赋值给父类);

当A ≦ B时,如果有f(B) ≦ f(A),那么f叫做逆变(父类赋值给子类);

如果上面两种关系都不成立则叫做不可变。

数组协变

代码

    @Test
    public void testZero(){
        Food food = new Fruit();
        // or
        food = new Meat(); // 即 把子类赋值给父类引用

        Fruit [] arrFruit = new Fruit[3];
        Food [] arrFood = new Food[3];
        arrFood=arrFruit; // 数组协变,把子类数组赋值给父类数组
        //arrFruit=arrFood;//error 不能逆变
    }

泛型协变与逆变

泛型

泛型没有内建的协变类型

代码

@Test
    public void testOne(){
        List<Meat> beefListRoot=new ArrayList<>();
       // List<Food> foodList=beefListRoot; //错误:不可协变,即子类list不能赋值给父类list
        List<Food> foodListRoot=new ArrayList<>();
       // beefListRoot=foodListRoot; //错误 : 不可逆变,即父类list不能赋值给子类list

    }

我们可以使用通配符实现泛型的协变和逆变

通配符协变

代码

 @Test
 public void testTwo(){
        List<? extends Food> foodList = new ArrayList<>();
        List<Apple> appleList = new ArrayList<>();
        foodList = appleList; // ok 协变,即子类list赋值给父类list
       // foodList.add(new Apple());//不能执行添加null 以外的操作,原因:反正法:beef也是food子类,但是不该加入苹果列表,否则get时类型转换异常,就有问题
        Food food = foodList.get(0); //ok, 把子类引用赋值给父类显然是可以的

    }

通配符逆变

代码

   @Test
    public void testThree(){
        List<? super Fruit> fruitList = new ArrayList<>();
        List<Food> foodList = new ArrayList<>();
        foodList.add(new Meat());

        fruitList = foodList; // ok 逆变,父类列表赋值给子类列表

        fruitList.add(new Apple()); // ok,只能添加 Fruit 或者 其子类
       // fruitList.add(new Food());// error, 只能添加 Fruit 或者 其子类

        //Fruit fruit = fruitList.get(0); // error,get出来的元素是Object类型
        Object obj = fruitList.get(0);// ok
    }

通配符的协变和逆变使用场景

如果参数化类型表示一个生产者,就使用。比如list.get(0)这种,list作为数据源producer;

如果它表示一个消费者,就使用。比如:list.add(new Apple()),list作为数据处理端consumer。

定义

Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。

如在代码中定义List和List等类型,在编译后都会变成List,JVM看到的只是List,而由泛型附加的类型信息对JVM是看不到的。

使用泛型获取返回值之前,泛型变量进行强转。

如:

public E get(int index) {  

    RangeCheck(index);  

    return (E) elementData[index];  

}

相关定义

  • 原始类型

就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型,无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换。

如: ArrayList<String> 原始类型为Object;ArrayList<T extend Apple> 原始类型为Apple;ArrayList<T super Apple> 原始类型为Object;

证明泛型擦除的案例

  • 1

    @Test
    public  void test() {
        List<String> ls=new ArrayList<String>();
        List<Integer> ln=new ArrayList<Integer>();
        //class java.util.ArrayList
        System.out.println(ls.getClass());
        //class java.util.ArrayList
        System.out.println(ln.getClass());
    
    }
  • 2

    @Test
    public void testTwo() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        ArrayList<Integer> list=new ArrayList<Integer>();
        //获取到list对象的add方法
        Method testTwo = list.getClass().getMethod("add",Object.class);
        //添加数据,定义泛型为整形,但是反射获取类型后可以进行添加String
        testTwo.invoke(list, "wqewqe");
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }

Gitte代码

泛型擦除:https://gitee.com/zhuayng/foundation-study/blob/develop/JavaBasis/Other/src/main/java/com/yxkj/other/modular/wildcard/erase/GenericErase.java

逆变与协变:https://gitee.com/zhuayng/foundation-study/blob/develop/JavaBasis/Other/src/main/java/com/yxkj/other/modular/wildcard/erase/transmute.java

参考

泛型擦除:https://blog.csdn.net/Dcwjh/article/details/102832280?utm_medium=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~default-1.no_search_link&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~default-1.no_search_link

逆变与协变:https://zhuanlan.zhihu.com/p/131602691;

https://blog.csdn.net/wangnanwlw/article/details/108711962?utm_medium=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~default-1.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~default-1.no_search_link