泛型基础
泛型的作用
在定义类、接口和方法时,可以附带类型参数,使其变成泛型类、泛型接口和泛型方法。与非泛型的代码相比,使用泛型可以让代码更健壮、更简洁、更通用。
基本的语法就不说了,主要看一下几种代码,是否可以确认出那种写法会正常编译,那种是编译报错。
类型擦除
泛型是在JDK1.5中引入的新特性,但是JDK1.5一下没有提供泛型支持,所以JAVA为了向下兼容,让编译器擦除Code属性中所有的泛型信息,泛型的信息会保留在类常亮池的属性中。
类型擦除在发生编译时,一般的操作是擦除所有类型信息,如果类型参数是有边界的,就会使用范围最大的边界,如果没有就直接使用Object。在必要时添加类型转换和桥方法
class Parent<T>{
private T t1;
public T getT1(){ return t1;}
public void setT1(T t){ this.t1 = t;}
public void func(T t){}
}
class Child<T extends Number> extends Parent<T>{
@Override
public void func(T t) {
}
}
public void test(){
Child<Integer> child=new Child<>();
Integer i=child.getT1();
}
------------类型擦除后----------
class Parent{
private Object t1;
public Object getT1(){ return t1;}
public void setT1(Object t){ this.t1 = t;}
public void func(Object t){}
}
class Child extends Parent{
public void func(Number t) {
}
//生成的桥方法
public void func(Object t){
func((Number)t);
}
}
public void test(){
Child<Integer> child=new Child<>();
Integer i=(Integer)child.getT1(); //类型强转
}
在上面的例子中就生成了桥方法。我们本意就是重写父类方法,实现多态,但是类型擦除后就只能变成重载了,所以虚拟机为了解决类型擦除和多态性的冲突,就产生了桥方法。
泛型的限制以及类型擦除的影响
变型:协变&逆变&不变
变型描述的是相同原始类型的不同参数化类型之间的关系。例如Integer是Number的子类型,请问List是List的子类型吗?
- 协变型:子类型关系保留
- 逆变型:子类型关系反转
- 不变型:子类型关系消除
例如:
List和List是不变型
Number[]和Integer[]是协变型
限定通配符
- <? extends>上界通配符
要想类型参数支持协变,就需要使用上界通配符,例如:
List<? extends Number> l1;
List<Integer> l2=new ArrayList<>();
l1=l2;
但是这样只能读取数据,不能调用包含类型参数的方法,也不能修改类型参数的字段。只能读取不能修改。
- <? super>下界通配符
要想类型参数支持逆变,需要使用下界通配符,例如:
List<? super Integer> l1;
List<Number> l2=new ArrayList<>();
l1=l2;
这个和协变相反,不能调用返回值为类型参数的方法,也不能访问类型参数的字段。只能修改不能读取。
- <?>无界通配符
无界通配符其实是<? extends Object>的缩写。
泛型代码的设计应遵循PECS原则:
- 如果只需要获取元素,使用 <? extends T>
- 如果只需要存储元素,使用<? super T>