前言
码代码时基本的用泛型还是没有问题,但没有深究过泛型的原理,学习后在此做个记录。
一、什么是泛型?为什么引入?
泛型是JDK5中引入的一种参数化类型特性。
为什么引入泛型?----水果案例
- 使代码更健壮(只要编译不提示错误,运行就不会报错)
- 代码更简洁(无需强转)
- 代码更灵活(多类型可复用)
二、泛型的定义和使用
1 泛型接口
public interface IPoint<T> {
void setPoint();
}
2 泛型接口的不同实现方式
2.1 泛型类
public class Point<T> implements IPoint<T> {
private T x ;
private T y ;
public void setX(T x){//作为参数
this.x = x ;
}
public void setY(T y){
this.y = y ;
}
public T getX(){//作为返回值
return this.x ;
}
public T getY(){
return this.y ;
}
@Override
public void setPoint() {
}
}
2.2 非泛型类
public class UPoint implements IPoint<String> {
private String x ;
private String y ;
public void setX(String x){//作为参数
this.x = x ;
}
public void setY(String y){
this.y = y ;
}
public String getX(){//作为返回值
return this.x ;
}
public String getY(){
return this.y ;
}
@Override
public void setPoint() {
}
}
3 泛型函数
//静态函数
public static <T>void StaticMethod(T a){
Log.d("demo","StaticMethod: "+a.toString());
}
//普通函数
public <T>void OtherMethod(T a){
Log.d("demo","OtherMethod: "+a.toString());
}
public static void main(String[] args) {
//静态方法
GenericDemo.StaticMethod("adfdsa");//使用方法一
GenericDemo.<String>StaticMethod("adfdsa");//使用方法二
//常规方法
GenericDemo staticFans = new GenericDemo();
staticFans.OtherMethod(new Integer(123));//使用方法一
staticFans.<Integer>OtherMethod(new Integer(123));//使用方法二
}
4 多泛型实现
在原来的T后面用逗号隔开,写上其它的任意大写字母即可,想加几个就加几个,比如:
class MorePoint<T,U,A,B,C> {}
但是,要注意的一个情况是若有多个限定的类型变量是范围中列出的所有类型的子类,若范围之一是类,则必须首先实现类的继承,否则会报错
static class A{};
static class A1{};
static interface B{};
static interface C{};
//static class D<T extends B & A & C>{} //编译报错
static class D1<T extends A & B & C>{}
三、泛型的本质及原理
下面代码输出结果为: true。
List 中添加 Integer 将不会通过编译,但是List与List在运行时的确是同一种类型,这是为什么呢?
public static void main(String[] args) {
Class c1 = new ArrayList<Integer>().getClass();
Class c2 = new ArrayList<String>().getClass();
System.out.print(c1 == c2);
}
原来Java的泛型是JDK5新引入的特性,为了向下兼容,虚拟机其实是不支持泛型,所以Java实现的是一种伪泛型机制,也就是说Java在编译期擦除了所有的泛型信息,这样Java就不需要产生新的类型到字节码,所有的泛型类型最终都是一种原始类型,在Java运行时根本就不存在泛型信息。
四、擦除机制
1 Java编译器具体是如何擦除泛型的?(ASM插件查看代码)
a 检查泛型类型,获取目标类型
b 擦除类型变量,并替换为限定类型
- 如果泛型类型的类型变量没有限定(),则用Object作为原始类型
- 如果有限定(),则用XClass作为原始类型
- 如果有多个限定(T extends XClass1&XClass2),则使用第一个边界XClass1作为原始类
c. 在必要时插入类型转换以保持类型安全
d 生成桥方法以在扩展时保持多态性
2、使用泛型及泛型擦除会带来哪些影响?
a 范型类型变量不能使用基本数据类型
比如没有ArrayList,只有ArrayList.当类型擦除后,ArrayList的原始类中的类型变量(T)替换成Object,但Object类型不能存放int值,示例如下图:
b 不能使用instanceof运算符
擦除后,ArrayList只剩下原始类型,泛型信息String不存在了,所有没法使用instanceof,示例如下图:
c 泛型在静态方法和静态类中的问题
d 泛型类型中的方法冲突
e 没法创建泛型实例,因为类型不确定
f 没有泛型数组:因为数组是协变,擦除后就没法满足数组协变的原则
五、泛型通配符(Rxjava中)
上面定义泛型类型时写了个Point类,创建实例时可以这么操作:
public void scene3(){
Point<String> stringPoint = new Point<String>();
Point<Integer> integerPoint = new Point<Integer>();
Point<Float> floatPoint = new Point<Float>();
Point<Double> doublePoint = new Point<Double>();
Point<Long> longPoint = new Point<Long>();
}
以上代码中不同类型的实例只能赋值给对应类型,于是就引入了无边界通配符。
5.1 无边界通配符?:用于填充泛型变量T
public void scene4(){
Point<?> point;
point = new Point<String>();
point = new Point<Integer>();
point = new Point<Float>();
point = new Point<Double>();
point = new Point<Long>();
}
以上代码示例中任意Point实例都可以赋值给point
5.2 上界通配符: ?extends T
public void scene5() {
Point<? extends Number> point;
point = new Point<Integer>();
point = new Point<Float>();
point = new Point<Double>();
point = new Point<Long>();
point = new Point<Number>();
// point = new Point<String>();//报错
// point = new Point<Object>();//报错
}
最后两行,当将T填充为String和Object时,赋值给point就会报错!
这里虽然是指派生自Number的任意类型,但 new Point();也可以成功赋值,这说明包括边界自身。
public void scene6() {
Point<? extends Number> point;
point = new Point<Integer>();
point.getX();
point.setX(new Integer(10));//提示错误
}
以上代码存在一个问题:利用<? extends Number>定义的变量,只可取其中的值,不可修改,这是为什么呢?
因为point的类型为 Point<? extends Number> point,那也就是说,填充Point的泛型变量T的为<? extends Number>,这是一个未知类型!用一个未知类型来设置内部值是不合理的。
但取值时,由于泛型变量T被填充为<? extends Number>,所以编译器能确定的是T肯定是Number的子类,编译器就会用Number来填充T。
也就是说,编译器只要能确定通配符类型就没问题,如果无法确定通配符的类型,就会报错。
5.3 下界通配符:?super T
class CEO extends Manager { }
class Manager extends Employee { }
class Employee { }
public void scene7() {
List<? super Manager> list;
list = new ArrayList<Employee>();
list = new ArrayList<Manager>();
list = new ArrayList<CEO>();// 报错
}
从示例代码可以看出因为CEO类已经不再是Manager的父类所以会报编译错误,且super关键字也是包括边界的。
上面extends通配符,能取不能存,那super通配符情况又怎样呢?
public void scene8() {
List<? super Manager> list = new ArrayList<Employee>();
list.add(new CEO());
list.add(new Manager());
list.add(new Employee());//报错
Object object = list.get(0);
Employee employee = list.get(0);//报错
}
以上因为Manager和CEO都是Employee的子类,在传进去list.add()后,会被强制转换为Employee!
编译器无法确定<? super Manager>的具体类型,但唯一可以确定的是Manager()、CEO()肯定是<? super Manager>的子类,所以可以add进去的。但Employee不一定是<? super Manager>的子类,所以不能确定,编译错误。
另外关于取的问题,list中item的类型为<? super Manager>,编译器能肯定的是<? super Manager>是Manger的父类;但不能确定是Object还是Employee类型。但无论是填充为Object还是Employee,它必然是Object的子类!所以Object object = list.get(0);不报错。
因为 list.get(0);肯定是Object的子类;而编译器无法判断list.get(0)是不是Employee类型的,所以Employee employee = list.get(0);报错。
这里虽然看起来是能取的,但取出来一个Object类型,是毫无意义的。所以我们认为super通配符:能存不能取;
5.4 总结? extends 和 ? super 通配符的特征
◆ 如果从一个数据类型里获取数据,使用 ? extends 通配符(能取不能存)
◆ 如果把对象写入一个数据结构里,使用 ? super 通配符(能存不能取)
◆ 如果既想存,又想取,那就别用通配符(PECS原则)。
六、总结
以下定义及区别
Plate
Plate
Plate<?>
Plate
Plate<? extends T>
Plate<? super T>