java 泛型深入
本文将由浅入深介绍java泛型机制,作者理解有限,有错误的地方欢迎指正。
java泛型基础
JDK1.5以后开始支持泛型,在引入泛型之后,原始的复杂类型可以划分为多个类型;
泛型与C++的模板机制类似,目的是实现更为抽象的解耦。更方便的创建通用的数据类型。
定义泛型
- 在类中定义泛型
// 此处定义了两个泛型,T和S,T为任意的数据类型,S约束此泛型必须继承自T
public class TestClassDefine<T, S extends T>{}
- 在方法中定义泛型
泛型类的方法可以在普通类和泛型类的内部加以定义
// 定义了两个泛型方法,同上面一样,S具有约束条件
public <T, S extends T> T testGenericMethodDefine(T t, S s){}
// 定义静态的泛型方法
public static <T> print(T[] arr);
- 创建泛型类
/**
* 泛型类对象
*/
class Pair<T, S> {
T first;
S second;
public Pair(T f, S s) {
first = f;
second = s;
}
@Override
public String toString() {
return "Pair [first=" + first + ", second=" + second + "]";
}
}
实例化泛型对象
- 直接使用具体的数据类型替代泛型。
Pair<String, Integer> p = new Pair<>("yangtr", 21);
System.out.println(p);
// output
// Pair [first=yangtr, second=21]
- 继承或实现接口时,若基类存在泛型,则子类也必须实现泛型
public class MyList<E> extends ArrayList<E> implements List<E> {...}
对泛型对象的界限限制
类型对象可以使用界限进行限制
// 此处对对象类型进行限制
// 提供两个或者多个修饰符,使用 & 连接
public static <T extends Comparable & Cloneable> void min(T[] arr){}
使用通配符类型对泛型对象进行约束
名称 | 语法 | 意义 |
---|---|---|
下界通配符 | ? extends B | B的任何子类型 |
上界通配符 | ? super B | B的任何超类型 |
无界统配符 | ? | 任何类型 |
// 对实现Comparable的超类型进行限制
// Comparable接口的类型参数是数组元素类型任意的任意超类型
public static <T extends Comparable<? super T>> T min(T[] arr){}
Java擦除机制
Java 虚拟机使用原始数据类型而不是泛型进行工作
泛型的原始数据类型是通过擦除变量获得的
类型擦除指的是通过类型参数合并,将泛型类型实例关联到同一份字节码上。
编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上。
类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且再必要的时候添加类型检查和类型转换的方法。
类型擦除可以简单的理解为将泛型java代码转换为普通java代码,只不过编译器更直接点,将泛型java代码直接转换成普通java字节码。
类型擦除的主要过程如下:
- 将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。
- 移除所有的类型参数。
JVM擦除后的类
class Pair{
Object first;
Object second;
public Pair(Object f, Object s) {
first = f;
second = s;
}
@Override
public String toString() {
return "Pair [first=" + first + ", second=" + second + "]";
}
}
擦除机制可以方便泛型程序与旧式程序接口,但也会带来一些问题
- 不能实例化泛型数组 T[] t = new T[10]; 非法,必须使用Object数组进行强制转换
- 不能实例化泛型对象 T t = new T(); 使用Object对象代替
总结
虚拟机中没有泛型,只有普通类和普通方法
所有泛型类的类型参数在编译时都会被擦除
创建泛型对象时请指明类型,让编译器尽早的做参数检查(Effective Java,第23条:请不要在新代码中使用原生态类型)
不要忽略编译器的警告信息,那意味着潜在的ClassCastException等着你。