Java 泛型中 <? extends T> 与 <? super T> 的区别

逆变与协变用来描述类型转换(type transformation)后的继承关系,其定义:如果A、B表示类型,f(⋅)表示类型转换,≤表示继承关系(比如,A≤B表示A是由B派生出来的子类)

f(⋅)是协变(covariant)的,当A≤B时有f(A)≤f(B)成立;

f(⋅)是逆变(contravariant)的,当A≤B时有f(B)≤f(A)成立;

f(⋅)是不变(invariant)的,当A≤B时上述两个式子均不成立,即f(A)与f(B)相互之间没有继承关系。

//让LongList赋值给NumberLsit
ArrayList<? extends Number> l=new ArrayList<Long>();
//让NumberList赋值给LongList
ArrayList<? super Long> ll=new ArrayList<Number>();

1.泛型的不变性

下面看个例子:

public class Tmp {
    public static void main(String[] args) {
        Number number;
        Long l=1L;
        number=l; //Long是Number的子类
        
        ArrayList<Number> numberArrayList;
        ArrayList<Long> longArrayList=new ArrayList<>();
        numberArrayList=longArrayList;  //编译报错
    }
}

为什么不能将longArrayList赋给numberArrayList呢?

我们想象下如果可以,我再在numberArrayList中加入一个Short对象,如下:

public class Tmp {
    public static void main(String[] args) {
        Number number;
        Long l=1L;
        number=l; //Long是Number的子类

        ArrayList<Number> numberArrayList;
        ArrayList<Long> longArrayList=new ArrayList<>();
        numberArrayList=longArrayList;  //编译报错
        
        numberArrayList.add(new Short("1"));
        numberArrayList.get(0);
    }
}

那么这里numberArrayList.get(0);到底取出来应该是什么类型的呢?

为了避免这种情况,Java把泛型定义为不可变的,即ArrayList<Long>和ArrayList<Number>并不具有Long和Number那样的关系。这是因为泛型是在编译期检测类型合法性的,而在实际运行期泛型类型被擦除了,所以在泛型看来ArrayList<Long>和ArrayList<Number>是两种不同的类型。

2.泛型的协变性(上界)

有时我们就是想让longArrayList赋给numberArrayList怎么办呢?

public class Tmp {
    public static void main(String[] args) {
        ArrayList<Long> longArrayList=new ArrayList<>();
        long l=sum(longArrayList);
    }

    /**
     * ? extends 使泛型可以协变
     * @param list 可接收Number及其子类List
     * @return
     */
    private static long sum(List<? extends Number> list){
        long res=0;
        for (Number n:list) {
            res+=n.longValue();
        }
        return res;
    }
}

协变可以让泛型的约束变得宽松,但也有相应的代价。

协变<? extend T> 不能存,只能取。即,

不能调用<? extend T>泛型类以T为形参的方法,只能调用以T为返回值的方法。

例如,我们修改上面的代码,

public class Tmp {
    public static void main(String[] args) {
        ArrayList<Long> longArrayList=new ArrayList<>();
        long l=sum(longArrayList);
    }

    /**
     * ? extends 使泛型可以协变
     * @param list 参数可接收Number及其子类List
     * @return
     */
    private static long sum(List<? extends Number> list){
        list.add(1.1F);//不能往里存
        list.add(1);//不能往里存
        long res=0;
        for (Number n:list) {
            res+=n.longValue();
        }
        return res;
    }
}

不能存,只能取是因为,这里list没有一个共同的子类,却有一个共同的父类Number,往外取时类型为Number或其父类总是没问题的。

JVM 在设计初期就没有考虑过泛型,因此对于 JVM 编译成的字节码来说,也没有泛型的概念,JVM 会使用一个占位符 CAP#1 来表示 p 接受一个 Number或子类,这里就通过 CAP#1 把类型擦除了。所以无论想往 p 插入任何类型都不可以(因为你不能赋值一个 CAP#1 类型)。但是你可以从 p 中往外取 CAP#1,因为 CAP#1 代表的是 Number 及其子类,因此往外取时,类型为 Number及其超类就总是安全的。

3.泛型的逆变(下界)

<? super Number> 只能存,不能取

public class Tmp {
    public static void main(String[] args) {
        ArrayList<Object> arrayList=new ArrayList<>();
        sum(arrayList);
    }

    /**
     * ? extends 使泛型可以协变
     * @param list 参数可接收Number及其子类List
     * @return
     */
    private static void sum(List<? super Number> list){
        list.add(1.1F);//能往里存
        list.add(1);//能往里存
        for (Object n:list) {
            System.out.println(n);
        }
    }
}

List<? super Number> list中下界是Number,那往里存粒度比Number小的都是安全的。但往外读取元素就费劲了,只有所有类的父类Object对象才能装下。但这样的话,元素的类型信息就全部丢失。

<? super Long> 下界为Long,代表里面放着Long及其父类, JVM 会使用一个占位符 CAP#1 来表示 p 接受一个Long或其父类, 这里就通过 CAP#1 把类型擦除了。 所以无论想从 p 拿出任何类型都不可以(因为你不不知道CAP#1是个什么类型)。 但是你可以往 p 插入CAP#1,因为 CAP#1 代表的是Long及其父类, 因此插入Long及Long的子类总是安全的

总结一下

public class Tmp {
    public static void main(String[] args) {
        //让LongList赋值给NumberLsit
        /*
         <? extends Number>
         代表里面放着Number及其子类,
         JVM 在设计初期就没有考虑过泛型,因此对于 JVM 编译成的字节码来说,也没有泛型的概念,
         JVM 会使用一个占位符 CAP#1 来表示 p 接受一个 Number或子类,
         这里就通过 CAP#1 把类型擦除了。
         所以无论想往 p 插入任何类型都不可以(因为你不能赋值一个 CAP#1 类型)。
         但是你可以从 p 中往外取 CAP#1,因为 CAP#1 代表的是 Number 及其子类,
         因此往外取时,类型为 Number及其超类就总是安全的。
         */
        ArrayList<? extends Number> l = new ArrayList<Long>();
        
        //让NumberList赋值给LongList
        /*
        <? super Long>
        代表里面放着Long及其父类,
        JVM 会使用一个占位符 CAP#1 来表示 p 接受一个Long或其父类,
        这里就通过 CAP#1 把类型擦除了。
        所以无论想从 p 拿出任何类型都不可以(因为你不不知道CAP#1是个什么类型)。
        但是你可以往 p 插入CAP#1,因为 CAP#1 代表的是Long及其父类,
        因此插入Long及Long的子类总是安全的
         */
        ArrayList<? super Long> ll = new ArrayList<Number>();
    }
}

4. PECS(Producer Extends Consumer Super)

Producer Extends 你写的类是主要作为生产者向外提供数据,那么就用 extends
Consumer Super 你写的类是主要作为消费者,需要数据,那么就用 super

Java面试 讲讲extends和super关键字_Ahuuua的博客-CSDN博客

猜你喜欢

转载自blog.csdn.net/Ahuuua/article/details/124648929