一、泛型基础
1)、什么示泛型
Java泛型是JDK1.5中引入的一个新特性,泛型提供了编译时类型安全检查机制,该机制允许程序员在编译时检到非法类型。
泛型的本质是参数类型,也就是说所操作的数据类型指定为一个参数类型。
泛型不存在与JNM虚拟机。
2)、为什么使用泛型
1、泛型可以增强
编译时
错误检查,减少因类型问题引发的运行异常(ClassCastException),因此泛型具有更强的类型检查。
2、泛型可以避免类型转换。
//没使用泛型
private void fun1(){
List nameList = new ArrayList();
nameList.add("XaoMing");
String name = (String) nameList.get(0);
}
//使用泛型
private void fun2(){
List<String> nameList = new ArrayList();
nameList.add("XaoMing");
String name = nameList.get(0);
}
3、泛型可以泛型算法,增加代码复用性。
private <T extends Comparable<T>> int countComparable(T[] array, T t2) {
int count = 0;
for (T e : array){
if (e.compareTo(t2) > 0){
count ++;
}
}
return count;
}
3)、Java中的泛型
1、泛型类
泛型类格式:
class name <T1,T2,...Tn>{}
泛型类之影响内部的普通方法和变量
2、泛型接口
泛型格式:
interface NameInterface<T> {}
3、泛型方法
定义格式:
private <K,V> boolean compare(Pair<K, V> p1, Pare<K, V>p2){}
调用格式:
Util.<K, V> compare(p1, p2)
//泛型类, 泛型接口的使用
class Men1<T, V> : MenInterface<V>{
var name: T
var str : V? = null
constructor(name: T) {
this.name = name
}
//泛型方法
fun <E> getStr1 (str: E) {
}
//泛型方法, 其中的T和类泛型中的T不是一个T
fun <T> getStr2 (str: T) {
}
private fun <T : Comparable<T>?> countComparable(array: Array<T>, t2: T): Int {
var count = 0
for (e in array) {
if (e!! > t2) {
count++
}
}
return count
}
override fun getMen(): V {
return str!!
}
override fun setMen(men: V) {
this.str = men
}
}
//泛型接口的使用
class Men2 : MenInterface<String>{
override fun getMen(): String {
return "XaoMing"
}
override fun setMen(men: String) {
TODO("Not yet implemented")
}
}
//泛型接口
interface MenInterface<V>{
fun getMen() : V
fun setMen(men : V)
}
4)、常见类型变量名称
- E : 表示
集合元素
类型(在Java集合框架中广泛运用) - K : 表示
关键字
类型 - N:表示
数字
类型 - V : 表示
值
类型 - T : 表示
任意
类型 - S,U,V:第二,第三,第四个类型
- ? :通配符,不能用在类中,可应用在方法和参数
5)、原始类型
缺少实际类型变量的泛型就是一个原始类型
如:
java
class Box<T>{}
Box box = new Box(); //这个Box就是Box<T>的原始类型
kotlin
class GG<T>
val gg: GG<*> = GG<Any?>()
二、泛型限制
对泛型变量的范围作出限制
1)、单一限制
<T extends X> //表示类型的上界,类型参数是X的子类
<T super X> //表示类型的下界,类型参数是X的超类
2)、多种限制
<T extends A & B & C>
- extends 表达的意识:这里指的是广义上的扩展,兼有
类型继承
和接口实现
之意。 - 多种限制下的格式语法要求:如果上限类型是一个类,必须第一位标识出,否着编译错误。且只能由一个类,多个接口
泛型算法实现的关键:利用受限类型参数
public class A {}
public interface B {}
public interface C {}
public class D<T extends A & B & C> { }
public static <T extends A & B & C> void setData(T data) { }
kotlin
interface A{
fun setName()
}
interface B{
fun setSex()
}
class D<T> where T: A, T: B {
}
三、PESC原则
//获取的值是Number,不能设置值
List<? super Number> list1;
//设置Number和子类,获取对象是Object
List<? extends Object> list2;
class Foot{}
class Fruit extends Foot {}
class Apple extends Fruit {}
class HongFuShi extends Apple {}
class Orange extends Fruit {}
class GenericType<T>{
private T date;
public T getDate() {
return date;
}
public void setDate(T date) {
this.date = date;
}
}
class FruitTest{
public static void print1(GenericType<? extends Fruit> fruit){
System.out.println(fruit.getDate().toString());
}
public static void print2(GenericType<? super Apple> apple){
System.out.println(apple.getDate().toString());
}
public static void use1(){
print1(new GenericType<Fruit>()); //true
print1(new GenericType<Apple>()); //true
print1(new GenericType<HongFuShi>()); //true
print1(new GenericType<Foot>()); //error 超过了上线
GenericType<? extends Fruit> genericType = new GenericType<>();
genericType.setDate(new Apple()); //error 不能设置数据
Fruit date = genericType.getDate(); //只能访问数据
}
public static void use2(){
print2(new GenericType<Apple>()); //true
print2(new GenericType<Fruit>()); //true
print2(new GenericType<HongFuShi>()); //error 超过线下
print2(new GenericType<Orange>()); //error 不是同一个类型
//因为 Apple和下限HongFuShi可以安全转型为Apple, Fruit不能安全转型
GenericType<? super Apple> genericType = new GenericType<>();
genericType.setDate(new Apple()); //true
genericType.setDate(new HongFuShi()); //true
genericType.setDate(new Fruit()); //error super 设置数据,只能设置自己和下限
Object date = genericType.getDate(); //获取的数据是Object类型
}
}
四、泛型擦除
功能:保证了泛型不在运行时出现
类型消除应用场合
-
编译器会把泛型类型中所有的类型参数替换成他们的上(下)限
,
如果没有对应类型作出限制,那么就会替换成Object类型。因此,编译出的字节码仅仅包含常规类,接口和方法。 - 多种限制<T extends A & B & C>擦除后用A。
- 在必要的时候插入类型转换以保证类型安全。
-
生成桥方法
以在扩展泛型时保持多态性。
Bridge Methods 桥方法 - 当编译一个扩展参数化类的类,或一个实现参数化类型接口的接口时,编译器有可能会创建一个合成方法,名为桥方法。它是类型擦除过程的一部分。
- java 的泛型伪泛型,JVM中不支持泛型,为了兼容低版本(JDK1.5以下)
五、编译
-
1)、用javac把java文件编译成class文件
javac DD.java
-
2)、用javap反编译class文件字节码
javap -c DD.class
六、知识点
1、) ArrayList<String> 、ArrayList<Object>和ArrayList<?>是否可以相互转化
ArrayList<String> list1 = new ArrayList<>();
ArrayList<Object> list2 = new ArrayList<>();
ArrayList<?> list3 = new ArrayList<>();
//不能同一类型,虽然String是Object的子类,但是ArrayList<String> 、ArrayList<Object>不是同一类型
list2 = list1;
//可以直接转换,因为?是通配符
list3 = list1;
但是泛型类可以继承或者扩展其他泛型类,比如List和ArrayList
2、) 限制
class D<T> {
private T data;
public D() {
//不能实例化类型变量
this.data = new T(); //error
}
//泛型类的静态上下文中类型变量失效, 所以静态域或方法里不能引用类型变量
//引文不知道泛型类型,泛型类型是做类初始化时候才知道的
private static T instance1(){} //error
//静态方法是放行方法可以
private static <T>T instance2(T data){ //true
return data;
}
//error
private <V extends Exception> void doWork1(V v){
try {
} catch (T e){ //error 不能捕获泛型类的实例
}
}
//true 泛型可以抛出异常
private <V extends Exception> void doWork2(V v) throws V{
try {
} catch (Exception e){
throw v;
}
}
public static void main(String[] argc){
//不能用基本类型实例化类型参数
D<double> d1; //error
//包装类型可以作为泛型
D<Double> d2 = new D<>(); //true
//运行时类型查询只适用于原始类型
//泛型不能用 instanceof
if (d1 instanceof D<Double>){} //error
if (d1 instanceof D<T>){} //error
D<String> d3 = new D<>();
//因为泛型擦除,获取泛型的原生类型进行比较
System.out.println(d2.getClass() == d3.getClass()); //true
//不能创建参数化类型的数组
//可以定义泛型数组,但不能new一个泛型数组
D<String>[] d4; //true
D<String>[] d5 = new D<String>[10]; //error
}
}
//泛型不能继承异常
class A<T> extends Exception { //error
}
//无法创建类型化实例
private static <E> void append(List<E> list) throws Exception{
E elem = new E(); //compile-time error
list.add(elem);
}
//通过反射创建一个参数化类型实例
private static <E> void append(List<E> list, Class<E> clazz) throws Exception{
E e = clazz.newInstance();
list.add(e);
}
七、虚拟机是如何实现泛型的
- 泛型思想早在C++语言的模板(Template)中就开始生根发芽,在Java语言处于还没有出现泛型的版本时,只能通过Object是所有类型的父类和类型强制转换两个特点的配合来实现类型泛化。,由于Java语言里面所有的类型都继承于java.lang.Object,所以Object转型成任何对象都是有可能的。但是也因为有无限的可能性,就只有程序员和运行期的虚拟机才知道这个Object到底是个什么类型的对象。在编译期间,编译器无法检查这个Object的强制转型是否成功,如果仅仅依赖程序员去保障这项操作的正确性,许多ClassCastException的风险就会转嫁到程序运行期之中。
- 泛型技术在C#和Java之中的使用方式看似相同,但实现上却有着根本性的分歧,C#里面泛型无论在程序源码中、编译后的IL中(Intermediate Language,中间语言,这时候泛型是一个占位符),或是运行期的CLR中,都是切实存在的,List<int>与List<String>就是两个不同的类型,它们在系统运行期生成,有自己的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型称为真实泛型。
- Java语言中的泛型则不一样,它只在程序源码中存在,在编译后的字节码文件中,就已经替换为原来的原生类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此,对于运行期的Java语言来说,ArrayList<int>与ArrayList<String>就是同一个类,所以泛型技术实际上是Java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型称为伪泛型。
- 将一段Java代码编译成Class文件,然后再用字节码反编译工具进行反编译后,将会发现泛型都不见了,程序又变回了Java泛型出现之前的写法,泛型类型都变回了原生类型
public static String method(List<String> str){
return "ok";
}
public static Integer method(List<Integer> str){
return 1;
}
- 开发工具编译器:检测是否为同一个类类型,检测方法名和参数是否相同。
- JDK编译器:检测是否为同一个类类型,检测方法名、返回类型和参数是否相同。
解析:在开发工具编译器中是同一个类型,因为参数List<T>擦除后是Object对象,所以参数相同。JDK编译器 中不是同种方法,因为返回类型不同。
- 上面这段代码是不能被编译的,因为参数List<Integer>和List<String>编译之后都被擦除了,变成了一样的原生类型List<E>,擦除动作导致这两种方法的特征签名变得一模一样。
- 由于Java泛型的引入,各种场景(虚拟机解析、反射等)下的方法调用都可能对原有的基础产生影响和新的需求,如在泛型类中如何获取传入的参数化类型等。因此,JCP组织对虚拟机规范做出了相应的修改,引入了诸如Signature、LocalVariableTypeTable等新的属性用于解决伴随泛型而来的参数类型的识别问题,Signature是其中最重要的一项属性,它的作用就是存储一个方法在字节码层面的特征签名,这个属性中保存的参数类型并不是原生类型,而是包括了参数化类型的信息。修改后的虚拟机规范要求所有能识别49.0以上版本的Class文件的虚拟机都要能正确地识别Signature参数。
- 另外,从Signature属性的出现我们还可以得出结论,擦除法所谓的擦除,仅仅是对方法的Code属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们能通过反射手段取得参数化类型的根本依据。
八、泛型擦除后恢复实例
因为在编译成CLASS是,在JVM有个
Signature
会对泛型弱记忆;然后可以Type
类中设置泛型类型,从而找到Signature 中记忆的泛型类型。
1)、导入google 的 gson 原理
implementation 'com.google.code.gson:gson:2.6.2'
2)、代码
Response<Date> dateResponse = new Response<>("200", "true", new Date("XaoHua"));
String str = gson.toJson(dateResponse);
System.out.println(str);
/**
* 用自定义的 TypeRefrence 代替 google 的 TypeToken
* 有花括号{}:代表匿名内部类,创建一个匿名内部内实力对向
* 无花括号{}:创建实例对象
*/
Response<Date> date = gson.fromJson(str, new TypeRefrence<Response<Date>>(){}.getType());
System.out.println(date.toString());
3)、自定义gson的TypeToken类型
/**
* 当构造方法为protected, 或者 abstract class 创建时必须带花括号{}
*
* @param <T>
*/
abstract class TypeRefrence<T> {
Type type;
protected TypeRefrence() {
//获得泛型类型
Type genericSuperclass = getClass().getGenericSuperclass();
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
//因为泛型类型可以定以多个A<T, v ...>所以是数组
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
type = actualTypeArguments[0];
}
public Type getType() {
return type;
}
}
4)、type类型子接口
-
TypeVariable
:泛型类型变量,可以泛型上下限等信息 -
ParameterizedType
:具体的泛型类型,可以获取元数据中泛型签名类型(泛型真实类型)。 -
GenericArrayType
:当需要描述的泛型是泛型类数组时,比如List[],Map[],此接口会作为Type的实现。 -
WildcardType
:通配符泛型,获取上下限信息。