前面讲述了一些泛型类,泛型接口,泛型方法的基础,这些并不是很重要,熟悉一些原理才是比较好的。
类型擦除
关于类型擦除这个东西,java中的泛型很多人都成为“伪泛型”,因为java中在编译的时候都是通过类型擦除来实现泛型。
怎么说呢,什么叫类型擦除呢?
下面这种情况:
我们这里通过T传入实际的对象,比如传入一个String类型,但是他会将其转成Object类型,凡是代码中有T的地方都是Object,而在返回T时就会自动插入T的实际类型进行强制转型。
我不知道jvm的发明者为什么要这样实现泛型,实现泛型的方式很多种。
如果这样操作的话,运行时其实根本不知道有泛型的存在,因为都是在编译的时候插入了代码进行类型的转换。
public class parent<T>{
public void d() {
System.out.println("d");
}
}
这样我们不知道T具体是什么的话,就根本不能进行有关T的实际方法变量的使用了。比如
创建T的对象就会出错,因为根本不知道T有没有无参构造器。
刚才提到了,既然底层是通过一些转换成Object对象实现的,那么我们也可以通过手动的联系Object对象来实现
比如,既然不能创建对象,同时也不能创建T的数组,我们就可以这样
当然这样还是访问不了传进来的具体T类中的方法,这也是没有办法呀,这就是类型擦除带来的麻烦,但是呢,也并不是没有办法啦,可以通过边界的确定来限制T的方法,比如这里传入的T编译器能够确定肯定是Object和Object的子类对吧,所以它的边界就是Object,没有比Object更大的类了。
边界限制
我不知道专业的术语是什么,毕竟英文文献里面的东西,我也不知道怎么翻译了,前面类型擦除其实自动限制了边界是Object,这里我们就是用通配符来实现边界限制,分为上边界限制,和下边界限制,分别是?extends ...和?super ....
? extends ....
这里的上边界通配符,主要将?的内容限制在...类的下面
//比如下面这种
public class a{
public static void main(String[] args){
List<? extends parent> p = new ArrayList<son>();
}
}
这里要说的就是这个代码片段呢,它看起来没啥错误,将边界限制在了parent以下,也就是说不用达到Object那种高度,我们通过这个泛型的表面对象使用上边界parent的里面所有方法。就像之前的限制在了Object一样了。
但是这里又造成了一个问题了,我们不值到传进来的是啥,也就是?不知道是啥,当我们使用add添加元素时,不知道啥,而java中没有自动向下转型,只有强制向下转型,编译器就认为不能向里面添加未知元素。这里怎么说呢,右边是ArrayList也就是实际在堆中开辟的对象,里面传入的也是son,左边虽然是继承的parent类型,但是parent的子类有很多,如果贸然去传入任何一个子类对象,运行时可能会出错,所以在这种不确定的情况下,干脆就让其不能再实现add方法。而又因为List是接口,调用p的add方法时,实际会调用ArrayList的add方法了。这时候是不是会起冲突
但是取出元素是可以的,因为返回的是T,传入的对象,也绝对是T的子类,可以自动向上转型。
? super ...
public void d() { List<? super parent> a = new ArrayList<parent>(); a.add(new son()); }
这个代码呢,就可以实现了add了,因为这个super限定了必定时parent的父类,所以我们add的只要时parent的子类都可以了。因为这里都可以向上转型为parent,可以确定为安全的。
但是get时parent tParent = (parent) a.get(1);就要这样强制转换为tParent了
感悟
这里我想说的感悟就是,不管这个怎么变,我觉得都是由于一点引起的。
就是java中的自动向上转型和强制向下转型限制的各种关系。也就是不能能自动向下转型
比如?extends.. 由于不知道传入的类型具体是什么,不能自动向下转型,而导致编译器报错,这个也是没有办法的,而对于?super...这种情况,也是由于不能自动向下转型,需要强制向下转型,使用get方法才可以。