定义:
JDK5引入的一种参数化类型特性
继承和实现接口可以多个
代码语言:javascript复制static class A{}
static interface B{}
static interface C{}
//类必须在接口的前面
static class D<T extends A & B & C>{}
泛型原理
泛型擦除:
- 做类型检查,T如果有做类型限制,会转化为第1种限制,否则会擦除为object
- 生成桥方法,里面调用对应的接口方法,调用的时候会进行类型的强转,转为T的限制类型
- 泛型擦除后,字节码中没有泛型信息了,但是类的常量池里保留了泛型信息。反射的时候提供了一套API可以拿到,比如getGenericType()
泛型带来的问题
- 泛型类型变量不能使用基本类型
比如没有ArrayList<int>,只有ArrayList<Integer>,当泛型擦除后,ArrayList的原始类中的类型变量T替换成了Object,但Object不能存放基本数据类型
- 不能使用instanceof运算符
因为泛型擦除后,ArrayList<String>只剩下原始类型,泛型信息String不存在了
- 泛型在静态方法和静态类中的问题 因为泛型类中的泛型参数的实例化是在定义泛型类型对象时候指定的,而静态成员是不需要使用对象来调用的,所有对象都没创建,无法确定泛型参数; 静态方法中是可以的,因为调用的时候可以确定参数中的泛型类型
- 泛型类型中的方法冲突 因为擦除后2个equales方法变成一样的了,参数都会变成object
@Override
public boolean equals(Object o) {
super.equals(o);
}
@Override
public boolean equals(T o) {
super.equals(o);
}
- 没法创建泛型实例,因为类型不确定
public static <E> void append(List<E> list){
//编译会报错
E element = new E();
list.add(element);
}
代码语言:javascript复制不过可以通过反射来创建
代码语言:javascript复制 public static <E> void append(List<E> list, Class<E> cls) throws Exception {
E element = cls.newInstance();
list.add(element);
}
- 没有泛型数组,因为数组遵循协变原则 协变:Apple extend Fruit,Apple[] 的父类是Fruit[]
泛型,继承和子类
- 给定两种具体的类型 A和B,无论A和B是否相关,MyClass和MyClass都没有半毛钱关系; 比如Apple继承自Fruit,那Plate<Fruit>和Plate<Apple>也没有任何关系;也就是说苹果是水果,但装苹果的盘子不是装水果的盘子
- 继承关系中,泛型可以有多个,但如果有一个泛型参数是一样的,继承关系就存在的,不会因为有的类多个个泛型参数,继承关系就不在了 比如Plate<Apple> <–AIPlate<Apple> <–BIgPlate<Apple> <-- ColorPlate<String, Apple>
泛型和通配符
通配符让泛型转型更灵活
- Plate<?> 非限定通配符,是一个泛型类型 ?表示未知,等价于 Plate<? extends Object>;副作用是既不能读也不能写;可以促使进行安全检查
- List和List<?>,前者不会进行安全检查,后者会进行类型的安全检查
限定通配符
- Plate<? extends T> 限定上届,能读不能写,类似于生产者;
- Plate<? super T> 限定下届,能写不能读,类似于消费者;
通配符总结
- 如果只需要从集合中获得类型T,使用<? extends T>;
- 如果只需要将类型T放到集合中,使用<? super T>;
- 既要获取又要放置元素,则不使用通配符
用<? extends Fruit>的后遗症
代码语言:javascript复制<? extends Fruit>是上届通配符,相当于“只读”
Plate<? extends Fruit> fruitPlate = plate(Plate<Apple> 类型)
//下面会报错,因为具体类型丢失了,只能是Fruit
fruitPlate.set(new Apple())
解决方案:可以用反射来set,但是安全性降低
代码语言:javascript复制Method set = friutPlate.getClass().getMethod("set", Object.class);
set.invoke(fruitPlate, new Banana())
//什么都能set,安全性降低
set.invoke(fruitPlate, new Beef())
面试题
1、Java泛型原理?什么是泛型擦除机制?
Java泛型是JDK5新引入的特性,为了向下兼容,虚拟机其实不支持泛型,所以Java实现的是伪泛型机制,也就是说Java在编译期擦除了所有的泛型信息,这样Java就不需要产生新的类型到字节码,所有的泛型类型最终都是一种原始类型,在Java运行时根本就不存在泛型信息
2、Java编译器具体是如何擦除泛型的
- 检查泛型类型,获取目标类型
- 擦除类型变量,并替换为限定类型。如果泛型类型的类型变量没有限定(<T>),则Object为原始类型;如果有限定(<T extends XClass>),则用XClass作为限定类型;如果有多个限定(T extends XClass1 & XClass2),则使用第一个边界XClass1作为原始类。
- 在必要时插入类型转换以保持类型安全
- 生成桥方法以在扩展时保持多态性
Kotlin泛型
Kotlin的泛型可以看文章:Android面试题之Kotlin泛型和reified关键字
END
点亮【赞和在看】,让钱和爱都流向你。
心里种花,人生才不会荒芜,如果你也想一起成长,请点个关注吧。
作者介绍
中年程序猿,十年移动端开发老司机,分享一线开发经验和知识,正在探索通过副业渡过中年危机
越努力越幸运,加油