本文主要是介绍泛型中上界与下界,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- 一、出现前景
- 二、通配符
- 三、上界 <? extends T>
- 三、下界 <? super T>
- 四、PECS原则
jdk部分源码中<? extends T>和<? super T>,一直不太明白是什么意思,针对该部分来系统学习下
一、出现前景
先看下面4个类,继承关系如下:
class A{}class B extends A{}class C extends B{}class D extends A{}
假设有testArr(A[] obj)和testGeneri(A a),针对该方法,形参为A,调用是可以传入A或者A的子类,如下所示都可以正常编译。
public void test(){testArr(new A[10]); //编辑成功,执行成功testArr(new B[10]); //编辑成功,执行成功testArr(new C[10]); //编译成功,执行报错testArr(new D[10]); //编译成功,执行报错testGeneri(new A());testGeneri(new B());testGeneri(new C());testGeneri(new D());
}private void testArr(A[] obj){obj[0] = new B();
}private void testGeneri(A a){}
假如换个需求,针对List集合的形参,方法如下所示:
private void testGeneriList(List<A> a){//D是A的子类,这条语句能正常执行a.add(new D());
}
如果在调用的时候,传入的是泛型为B对象的list集合,则报错如上所示,第二条语句乍一看感觉是没有问题的,因为B继承至A,在需要List<A>的地方传入List<B>,却发现这里编译不通过。
对于数组来说,数组是协变的,也就是说对于B extends A,要求A[ ]的地方可以传入B[ ],即A是B的父类,A[ ]也是B[ ]的父类,但是这个规则对于泛型是不适用的,泛型并不是协变的。
为什么对于集合来说,不适用于这条规则呢?------泛型擦除
我们知道的是 Java 中的泛型基本上都是在编译器这个层次来实现的。在生成的 Java 字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的 List<Object> 和 List<String> 等类型,在编译之后都会变成 List。JVM 看到的只是 List,而由泛型附加的类型信息对 JVM 来说是不可见的。
类型擦除过程简单来看就是:首先是找到用来替换类型参数的具体类。这个具体类一般是 Object。如果指定了类型参数的上界的话,则使用这个上界,否则就是Object。
通过上面的分析可以看出,List<String>并不继承于List<Object>,但是对于某些地方,比如上面的testGeneriList方法来说,为了提高复用性,形参为List<A>,只能传入List<A>或者ArrayList<A>,无法使用具体的泛型对象,如List<B>。
针对该类问题,引入了?通配符,并通过<? extends T>和<? super T>确定其上下界限。
二、通配符
?代表的是未知类型,List<?>表示List集合中的元素是未知的,即可能是String,也可能是Integer。但是需要注意的是List<?>并不是等同于List<Object>。不能使用new ArrayList<?>()来创建对象,因为编译器并不知道里面的具体类型。
引入泛型之后的类型系统增加了两个维度:一个是类型参数自身的继承体系结构,另外一个是泛型类或接口自身的继承体系结构。第一个指的是对于 List<String> 和 List<Object> 这样的情况,类型参数 String 是继承自 Object 的。而第二种指的是 List 接口继承自 Collection 接口。对于这个类型系统,有如下的一些规则:
- 相同类型参数的泛型类的关系取决于泛型类自身的继承体系结构。即 List<String> 是 Collection<String> 的子类型,List<String> 可以替换 Collection<String>。这种情况也适用于带有上下界的类型声明。
- 当泛型类的类型声明中使用了通配符的时候, 其子类型可以在两个维度上分别展开。如对 Collection<? extends Number> 来说,其子类型可以在 Collection 这个维度上展开,即 List<? extends Number> 和 Set<? extends Number> 等;也可以在 Number 这个层次上展开,即 Collection<Double> 和 Collection<Integer> 等。如此循环下去,ArrayList<Long> 和 HashSet<Double> 等也都算是 Collection<? extends Number> 的子类型。
- 如果泛型类中包含多个类型参数,则对于每个类型参数分别应用上面的规则。
三、上界 <? extends T>
限定了泛型范围为T和T的子类,下面代码均能正常执行
public void test1(){test3(new ArrayList<A>()); //编译成功test3(new ArrayList<B>()); //编译成功test3(new ArrayList<C>()); //编译成功test3(new ArrayList<D>()); //编译成功}public void test3(List<? extends A> list){}
看到这,我们不难想到一个问题,假如针对test3方法来说,lsit存放的是A或者A的子类,如果我们在该方法中加入下面的代码:
list里存放的是A或者A的子类,假设上面的代码能编译通过,如果调用test3方法的地方,传入的是List<C>,则在方法中的add方法添加的是一个B类型的对象,泛型为C的集合添加B类型的对象是错误的。也就是说编译器会尽可能的检查可能存在的类型安全问题,对于确定是违反相关原则的地方,会给出编译错误,当编译器无法判断类型的使用是否正确的时候,会给出警告信息。
但是对于从集合中取值,取得都是A或者A的子类对象,可以直接用A接收,如下:
所以针对于上界来说,只能取,不能存
三、下界 <? super T>
同理,下界就是限定了范围为T和T的父类的所有类型,如下:
public void test1(){test4(new ArrayList<A>()); //编译成功test4(new ArrayList<B>()); //编译成功test4(new ArrayList<C>()); //编译成功test4(new ArrayList<D>()); //编译报错*******}public void test4(List<? super C> list){}
如下图:对于下界来说,list里存的都是B或者B的父类,往list中添加的时候,可以存入B,但是却不能存入B的父类型A,看到这里我们可能会有个疑问,list不是限定了泛型为B或者B的父类,A是满足条件的啊,为啥编译器这里报错了,其实这里也好理解,类比于上界不能存的例子,我们这里假设list.add(new A())能正常编译通过,在调用test4的地方如果传入了一个List<B>,那么在方法中,一个B类型集合却添加了A类型对象,即使A为B的父类,这是不合法的,编译器检查到了这里存在类型安全问题,违反了相关原则,给出编译错误。
list存入的都是B的父类,也就是说B是当前list集合中最小的单元,无论传入B本身,还是B的父类亦或者是B的父类的父类,都可以添加B对象,所以上界这里能存,但是只能存入最小泛型对象。
上界取值,由于list是B的父类,那么最顶层就是Object,所有可以取值,但是取出来Object类型:
四、PECS原则
通过上面的例子我们能得出结论:
- 上界<? extends T>不能存,只能往外取,取出来的东西只能放到T或T的父类中。
- 下界<? super T>可以往里存,但往外取只能放在Object对象里。
回看上面那句话:
编译器会尽可能的检查可能存在的类型安全问题,对于确定是违反相关原则的地方,会给出编译错误,当编译器无法判断类型的使用是否正确的时候,会给出警告信息。
这个原则就是PESC((Producer Extends Consumer Super))原则:
- Producer Extends 生产者使用Extends来确定上界,往里面放东西来生产
- Consumer Super 消费者使用Super来确定下界,往外取东西来消费。
(上界生产,下界消费)理解起来就是:
如果在一个情景下限制只能从里面取数据(生产者),那么可以使用上界,这样取出来的对象都是T或者T的子类,通过向上转型也都可以用T接收;
如果想限制只能向list里面写数据(消费者),那么可以使用下界,这样存入的数据都是T或者T的父类,无论对象实际类型是什么,都可以向里面写入T类型对象。
总结:
1、频繁往外读取内容的,适合用上界Extends,即extends 可用于返回类型限定,不能用于参数类型限定。2、经常往里插入的,适合用下界Super,super 可用于参数类型限定,不能用于返回类型限定。3、带有 super 超类型限定的通配符可以向泛型对象用写入,带有 extends 子类型限定的通配符可以向泛型对象读取
参考文章:
https://blog.csdn.net/qq_45545968/article/details/122464496
https://blog.csdn.net/jdsjlzx/article/details/70479227
这篇关于泛型中上界与下界的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!