概述
Java泛型是JDK5中引入的一种参数化类型特性
参数化类型
解释:把类型当做参数一样传递
泛型的副作用:<数据类型>只能是引用类型
使用泛型后有以下的好处:
- 代码更健壮(只要编译期没有警告,那么运行期就不会出现ClassCastException)
- 代码更简洁(不用强转)
- 代码更灵活、复用
泛型的使用
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add(3);//报错
}
泛型中的类型在使用时指定,不需要强制类型转换(类型安全,编译器会检查类型)
泛型擦除
先看一段代码:
public static void main(String[] args) {
ArrayList<String> strList = new ArrayList<>();
ArrayList<Integer> intList = new ArrayList<>();
System.out.println(strList.getClass() == intList.getClass());
}
打印如下:
通过打印可以得知,strList和intList类型是一样的,但是如果使用strList = intList进行赋值,会报错,提示类型不匹配,这就是泛型擦除。
通过ASM查看 ArrayList 的add方法如下:
可以看到我们定义的泛型T都被擦除成了Object,当我们自定义泛型类如下:
public class GenericApi<T extends Comparable<T>> {
private ArrayList<T> data = new ArrayList<>();
public void add(T t){
data.add(t);
}
public void reduce(T t){
if (data.contains(t)){
data.remove(t);
}
}
}
再看生成字节码文件的add方法:
这里的泛型T被擦除成了Comparable,所以结论如下:在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如 则会被转译成普通的 Object 类型,如果指定了上限如 <T extends Comparable>则类型参数就被替换成类型上限。
泛型与反射
先看一段代码:
public class GenericTest {
Map<String,String> map;
public static void main(String[] args) throws Exception {
Field field = GenericTest.class.getDeclaredField("map");
System.out.println(field.getGenericType());
System.out.println(field.getGenericType() instanceof ParameterizedType);
ParameterizedType type = (ParameterizedType) field.getGenericType();
System.out.println(type.getRawType());
for (Type argument: type.getActualTypeArguments()) {
System.out.println(argument);
}
System.out.println(type.getOwnerType());
}
}
打印如下:
从上面的打印能看出来,泛型虽然被擦除了,但是还是能通过反射拿出来,因为类的常量池中还是保留泛型信息。
泛型PESC原则
如果你只需要从集合中获取类型T,使用<? extends T> 通配符
如果你只需要将类型T放到集合中,使用<? super T> 通配符
如果你既要获取又要放置元素,则不能使用任何通配符。例如ArrList
PESC即Producer extends Consumer super,为了便于记忆
总结
泛型原理
Java的泛型是JDK5新引入的热性,为了向下兼容,虚拟机其实是不支持泛型,所以Java实现的是一种伪泛型机制,也就是说Java在编译期擦除了所有的泛型信息,这样Java就不需要产生新的类型到字节码,所有的泛型类型最终都是一种原始类型,在Java运行时根本就不存在泛型信息。
擦除泛型过程
1.检查泛型类型,获取目标类型
2.擦除类型变量,并替换为限定类型
- 如果泛型类型的类型变量没有限定,则用Objcet作为原始类型
- 如果有限制(T extends XClass),则用Xclass作为原始类型
- 如果有多个限定(T extends XClass1&XClass2),则使用第一个边界XClass1作为原始类
3.在必要时插入类型转换以保持类型安全
4.生成桥方法以在扩展时保持多态性