简介
在实际开发中会遇到一些类或者方法在处理数据时,数据具有多样性,如果针对每个数据都新建一个类或者方法来处理就显得颇为麻烦。
常见的场景就是list,里面会存String,Integer等等数据。为了解决这个问题,需要将处理的数据类型告诉这些类或者方法,即参数化类型,把类型当成参数,别名叫泛型,意思是泛化类型。
简单使用
告诉list要处理的类型是String
List<String> strList = new ArrayList<>();
strList.add("a");
strList.add("b");
strList.add("c");
由于在声明时传递给list的类型参数是String,所以在使用时可以直接当做String来使用
System.out.println(strList.get(0).length);
下面示例中使用的Person的子类是Teacher,Teacher的子类是Student.
java中的泛型的由来
java中的泛型有个历史遗留问题,java中的泛型一开始的版本中是没有的,1.5之后才加入,为了兼容以前的版本,在设计时jvm中对于泛型做了类型擦除,即不会记住声明时的类型
这边在泛型内虽然写了Person或者Teacher,但是实际编译时都是list,里面的类型是他们的默认上限,即Object
List<Person> pList = new ArrayList<>();
List<Teacher> tList = new ArrayList<>();
为了兼容版本,使用类型擦除带来的问题
由于java的泛型是伪泛型,所以下面那种本该正常的写法却变错了,因为java默认是不支持型变的,即不支持协变和逆变。
协变:Teacher是Student的父类,那么List<Teacher>也是List<Student>的父类
逆变:Teacher是Person的子类,那么List<Teacher>也是List<Person>的子类
协变,会报错
List<Teacher> tList = new ArrayList<Student>();
逆变,会报错
List<Teacher> tList = new ArrayList<Person>();
java支持型变的方式
java提供了?加上extends或者super来解决这个问题,?extends 来支持协变,? super 支持逆变。
协变,不会报错,extends声明上限
List<? extends Teacher> tList = new ArrayList<Student>();
逆变,不会报错,super声明下限
List<? super Teacher> tList = new ArrayList<Person>();
java中型变后带来的问题
虽然使用extends和super能够支持型变了,但是类型擦除的问题还在,即擦除到上限
所以List<? extends Teacher>,类型被擦除成Teacher,也就是他能接受的类型范围是[Teacher,低线]。
List<? super Teacher>的上限还是Object,他接受的类型范围就是[Object,Teacher]
这就带来下面一些问题,首先看协变带来的问题,在此之前提一下多态,java中支持运行时多态,父类的引用可以指向子类的实例
//协变
List<? extends Teacher> tList = new ArrayList<Student>();
/*
*由于类型擦除,协变后list类型可能是[Teacher,低线]范围的任何一种
*jvm不知道具体的类型是啥,也就没法插入任何数据。
*/
tList.add(new Student()); //会报错
tList.add(new Teacher()); //会报错
//只有和类型无关的可以,比如
Teacher teacher = tList.get(0);
//由于擦除到上限,和多态的存在,不用管list里面存的是啥,Teacher一定是他的父类(或者是Teacher本身),所以能够取出来用teacher的引用指向他。
逆变super和协变问题相反,由于super只定义了下限,所以上限还是Object。
/*
*逆变
*现在tList的 ?superTeacher
*代表它数据类型的范围可能是[Object,Teacher]
*/
List<? super Teacher> tList = new ArrayList<Person>();
/*
*如果插入数据是[Object,Teacher]范围内的
*jvm和之前extends插入数据问题一样,是不知道具体哪个类型的。
*但是如果插入数据是[Teacher,低线]范围内的,包括Teacher。
*就可以插入了,这还是多态存在的结果,当插入数据在[Teacher,低线]范围内,
*就不用管list本身是Object到Teacher范围中哪种类型了,因为他必定是插入数据的父类,也就可以插入了。
*/
tList.add(new Student());
tList.add(new Teacher());
//但是被擦除成Object,所以从list中拿出来的都是Object来接收,既然是Object接收,泛型失去了他的原意了,一般就认为他不可以取数据
Object object = tList.get(0);
总结
java协变(? extends XXX)带来的问题是只能读取不能修改
java逆变(? super XXX) 带来的问题是能够修改一些数据,但是不能读取(如果知道类型拿到Object强转也是可以的)。
这就是PECS法则:Producer-Extends、Consumer-Super
extends的只能生产,super的只能查看