- 泛型基本概念
- 1由来
- 2伪泛型
- 3泛型的使用
- 4一些要求及规则
- 原理
- 1类型擦除
- 2重要用反射来看泛型的机制甚至可以破坏
- 3原始类型
- 类型擦除引起的问题及解决办法
- 1先检查再编译
- 2类型检查的依据
- 3泛型参数化类型没有继承关系
- 类型擦除与多态的冲突和解决方法
- 1桥方法
- 泛型在静态类和静态方法中的问题
1、泛型基本概念
1.1、由来
泛型是JDK 1.5的一项新特性,在语言处于还没有出现泛型的版本时,只能通过Object是所有类型的父类和类型强制转换两个特点的配合来实现类型泛化。例如在哈希表的存取中,JDK 1.5之前使用HashMap的get()方法,返回值就是一个Object对象,由于Java语言里面所有的类型都继承于java.lang.Object,那Object转型为任何对象成都是有可能的。但是也因为有无限的可能性,就只有程序员和运行期的虚拟机才知道这个Object到底是个什么类型的对象。在编译期间,编译器无法检查这个Object的强制转型是否成功,如果仅仅依赖程序员去保障这项操作的正确性,许多ClassCastException的风险就会被转嫁到程序运行期之中。
1.2、伪泛型
泛型技术在C#和Java之中的使用方式看似相同,但实现上却有着根本性的分歧,C#里面泛型无论在程序源码中、编译后的IL中(Intermediate Language,中间语言,这时候泛型是一个占位符)或是运行期的CLR中都是切实存在的,List<int>与List<String>就是两个不同的类型,它们在系统运行期生成,有自己的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型被称为真实泛型。
Java语言中的泛型则不一样,它只在程序源码中存在,在编译后的字节码文件中,就已经被替换为原来的原始类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此对于运行期的Java语言来说,ArrayList<int>与ArrayList<String>就是同一个类。所以说泛型技术实际上是Java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型被称为伪泛型。
1.3、泛型的使用
- 泛型类
- 泛型接口
- 泛型方法
1.4、一些要求及规则
- 不能使基本类型
- 不管该限定是类还是接口,统一都使用关键字extends
- 可以使用&符号给出多个限定
- 如果限定既有接口也有类,那么类必须只有一个,并且放在首位置
详见:
2、原理
2.1、类型擦除
Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。
如在代码中定义的List<object>和List<String>等类型,在编译后都会编程List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。
2.2、重要:用反射来看泛型的机制(甚至可以破坏)
在程序中定义了一个ArrayList泛型类型实例化为Integer的对象,如果直接调用add方法,那么只能存储整形的数据。不过当我们利用反射调用add方法的时候,却可以存储字符串。这说明了Integer泛型实例在编译之后被擦除了,只保留了原始类型。
2.3、原始类型
原始类型(raw type)就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型。无论何时定义一个泛型类型,相应的原始类型都会被自动地提供。类型变量被擦除(crased),并使用其限定类型(使用extends的,如果extends多个则原始类型就用第一个边界的类型变量来替换)替换,无限定的变量用Object替换。
3、类型擦除引起的问题及解决办法
3.1、先检查、再编译
因为类型擦除是在编译期完成的,在运行的时候就会忽略泛型,为了保证在运行的时候不出现类型错误,就需要在编译之前就检查是否满足泛型要求(类型检查)。
3.2、类型检查的依据
以上两种情况都没有错误:第一种情况,在使用arrayList1的时候与完全使用泛型参数一样的效果,因为new ArrayList()只是在内存中新开辟一个存储空间,它并不能判断类型,而真正涉及类型检查的是它的引用,所以在调用arrayList1的时候会进行类型检查。同理,第二种情况,就不会进行类型检查。
3.3、泛型参数化类型没有继承关系
- 第一种情况,可以扩展为一下形式:
假设它编译没错,那么当我们使用arrayList2引用用get()方法取值的时候,返回的都是String类型的对象(类型检测是根据引用来决定的),可是它里面实际上已经被我们存放了Object类型的对象,这样就会出现ClassCastException。
- 第二种情况,同样扩展为:
虽然不会出现ClassCastException,但是假设它编译没错,那么当我们使用arrayList2引用用get()方法取值的时候,返回的都是Object类型的对象,需要进行类型转换才行,这样,泛型就完全没有作用了。
4、类型擦除与多态的冲突和解决方法
现在看来我们在子类中重写了父类的两个方法,而实际上,经过类型擦除之后:
可以看到,父类和子类的方法中参数类型不同,所以如果是在普通的继承关系中,这完全不是重写,而是重载;但是如果在泛型中呢?
如果是重载,那么子类中两个setValue方法,一个是参数Object类型,一个是Date类型,可是我们发现,根本就没有这样的一个子类继承自父类的Object类型参数的方法。所以说,在泛型中确实是重写了,而不是重载。
4.1、桥方法
通过编译源代码会发现DataInter最后会有四种方法,其中两个是编译器自己生成的桥方法,它的参数类型是Object,也就是说,子类中真正覆盖/重写父类两个方法的就是这两个我们看不到的桥方法。而打在我们自己定义的setvalue和getValue方法只不过是假象。而桥方法的内部实现,就只是去调用我们自己重写的那两个方法。
5、泛型在静态类和静态方法中的问题
泛型类中的静态方法和静态变量不可以使用泛型类所声明的泛型类型参数。
因为泛型类中的泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用。对象都没有创建,如何确定这个泛型参数是何种类型,所以当然是错误的。