一篇文讲明白Java泛型
泛型(Generic)是JDK5引入的一种参数化类型特征,是Java编程语言的强大功能。它们提高了代码的类型安全性,使你在编译时可以检测到更多错误。泛型通过在编译时检测到更多错误来增加代码的稳定性。
1. 为什么要使用泛型
简而言之,泛型在定义类,接口和方法时使类型(类和接口)成为参数。与方法声明中使用的更熟悉的形式参数非常相似,类型参数为你提供了一种使用不同输入重复使用相同代码的方法。区别在于形式参数的输入是值,而类型参数的输入是类型。
2. 泛型的好处
- 代码更健壮(只要编译器没有警告,那么运行期就不会出现ClassCastException)
- 代码更简洁(不用强转)
- 代码更灵活,复用
3. 泛型长什么样
3.1. 泛型接口
public interface Plate<T>{
public void set(T t);
public T get();
}
3.2. 泛型类
public class AIPlate<T> implements Plate<T>{
private List<T> items = new ArrayList<T>(10);
}
3.3. 泛型方法
public <T> AIPlate<T> getAIPlate(){
return new AIPlate<T>();
}
4. 泛型擦除
4.1. Java泛型的原理?什么是泛型擦除机制?
Java的泛型是JDK5新引入的特性,为了向下兼容,虚拟机其实是不支持泛型的,所以Java实现的是一种伪泛型机制,也就是说Java在编译器擦除了所有的泛型信息,这样Java就不需要产生新的类型到字节码,所有的泛型类型最终都是一种原始类型,在Java运行时根本就不存在泛型信息。
4.2. Java编译器具体是如何擦除泛型的?
- 检查泛型类型,获取目标类型
- 擦除类型变量,并替换为限定类型
如果泛型类型的类型变量没有限定(< T >),则用Object作为原始类型
如果有限定< T extends XClass>,则用XClass作为原始类型
如果有多个限定< T extends XClass1 & XClass2>,则使用第一个边界XClass1作为原始类 - 在必要时插入类型转换以保持类型安全
- 生成桥方法以在扩展时保持多态性
4.3. 使用泛型以及泛型擦除带来的影响(副作用)
4.3.1. 泛型类型变量不能使用基本数据类型?
比如没有ArrayList< int >,只有ArrayList< Integer >,当类型擦除后,ArrayList的原始类型中的类型变量< T >替换成了Object,但Object类型不能存放int值。
4.3.2. 不能使用instanceof运算符
因为擦除后,ArrayList只剩下原始类型,泛型信息String不存在了,所以没法使用instanceof
4.3.3. 泛型在静态方法和静态类中的问题
因为泛型类中泛型参数的实例化是在定义泛型类型对象(比如ArrayList< Integer >)的时候指定的,而静态成员是不需要使用对象来调用,所以对象都没创建,如何确定这个泛型参数是什么。
4.3.4. 泛型类型中的方法冲突
因为擦除后两个equals方法变成一样的了。
4.3.5. 没法创建泛型实例
因为类型不确定。
4.3.6. 没有泛型数组
因为数组是协变,擦除后就没法满足数组协变的原则。
协变:例如A extends B,那么A[ ]的父类是B[ ]。
5. Java泛型PECS原则
- 如果你只需要从集合中获得类型T,使用<? extends T>通配符
- 如果你只需要将类型T放到集合中,使用<? super T>通配符
- 如果你既要获取又要放置元素,则不使用任何通配符。
PESC即Producer extends Consumer super。
非限定通配符:AClass<?>是一个未知的泛型类型,等价于AClass<? entends Object>
限定通配符:AClass<? extends T> 上界;AClass<? super T> 下界。