• java中关于协变和逆变的实现


    介绍

    协变和逆变描述的是类型转换后的继承关系。
    定义A,B两个类型,A是B的子类,f(A) 表示类型转换后的类型,如List

    • 协变 A <= B,f(A) <= f(B) 成立
    • 逆变 A <= B,f(A) >= f(B) 成立
    • 不变 A <= B,都不成立

    数组的协变和逆变

    public class Client {
    
      public static void main(String[] args) {
        Fruit[] fruits = new Apple[5];
        fruits[0] = new Apple();
        fruits[1] = new Fruit();
      }
    
      static class Fruit {
    
      }
    
      static class Apple extends Fruit {
    
      }
    
      static class RedApple extends Apple {
    
      }
    
      static class Orange extends Fruit {
    
      }
    }
    

    Apple 是 Fruit 的子类,Fruit[] fruits = new Apple[5] 成立,说明数组是协变的。上面的程序在运行时会抛出异常 ArrayStoreException,这是因为数组的实际类型是 Apple,但实际存放的类型为 Fruit。

    容器的协变和逆变

    public class Client2 {
    
      public static void main(String[] args) {
        //编译报错
        List<Fruit> fruitList = new ArrayList<Apple>();
        //编译报错
        List<Apple> appleList = new ArrayList<Fruit>();
      }
    }
    

    可以看出泛型既不是协变的,也不是逆变的,而是不变的,但这就相当于放水果的盘子不能放苹果一样,感觉很别扭,所以java引入了通配符来支持协变和逆变。

    public class Client2 {
    
      public static void main(String[] args) {
        List<? extends Fruit> fruitList = new ArrayList<>();
        //编译报错
        fruitList.add(new Apple());
        //编译报错
        fruitList.add(new Fruit());
        Fruit fruit = fruitList.get(0);
      }
    }
    

    使用<? extends Fruit> 来支持协变,表示Fruit及子类,但编译器不能实际确定容器中元素的类型,可能是 Fruit,可能是 Apple,编译器不能确定要添加的元素是否和容器中元素是否能匹配,所以就不允许添加了,只能获取。(这可能是一个放苹果的盘子,也可能是放橙子的盘子,这个时候苹果和橙子都不能往里放,但我们可以说取出来的一定是一个水果)。

    public class Client2 {
    
      public static void main(String[] args) {
        //逆变
        List<? super Apple> fruitList = new ArrayList<>();
        fruitList.add(new Apple());
        fruitList.add(new RedApple());
        //编译报错
        Apple apple = fruitList.get(0);
        //编译通过
        Object obj = fruitList.get(0);
      }
    }
    

    使用<? super Apple> 来支持逆变,表示Apple及父类,但编译器不能实际确定容器中元素的类型,可能是 Apple,可能是 Fruit。编译器不能确定容器中的元素的类型,所以就不允许获取了,只能添加。(这可能是放苹果的盘子,也可能是放水果的盘子,两种盘子都可以放苹果,既然可以放苹果,肯定可以放红苹果,但我们不能确定取出来的是苹果还是红苹果,或其他水果)。

    关于什么时候使用协变,逆变,《Effective Jave》有总结
    producer-extends,consumer-super,简单来说就是作为生产者时使用extends,作为消费者时使用super。Collections.copy()就是一个经典的例子

    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
            int srcSize = src.size();
            if (srcSize > dest.size())
                throw new IndexOutOfBoundsException("Source does not fit in dest");
    
            if (srcSize < COPY_THRESHOLD ||
                (src instanceof RandomAccess && dest instanceof RandomAccess)) {
                for (int i=0; i<srcSize; i++)
                    dest.set(i, src.get(i));
            } else {
                ListIterator<? super T> di=dest.listIterator();
                ListIterator<? extends T> si=src.listIterator();
                for (int i=0; i<srcSize; i++) {
                    di.next();
                    di.set(si.next());
                }
            }
        }
    

    dest作为消费者,只能放数据,src作为生产者,只能取数据。

  • 相关阅读:
    docker vm 性能优劣
    Jeecg-Boot Spring Boot
    MyBatis-Plus
    Eclipse lombok java
    MySQL 高可用性—keepalived+mysql双主
    如何使用 Docker 来限制 CPU、内存和 IO等资源?
    Requires: libc.so.6(GLIBC_2.14)(64bit)
    项目管理、软件、禅道 VS JIRA
    解决Window安全中心对Kitematic-0.17.3-Ubuntu.zip提示病毒,但无法删除的问题。
    正则表达式 贪婪模式
  • 原文地址:https://www.cnblogs.com/strongmore/p/13946414.html
Copyright © 2020-2023  润新知