泛型
Java泛型(generics)是JDK 5中引入的一个新特性,泛型提供了编译时类型安全监测机制,该机制允许程序员在编译时监测非法的类型。使用泛型机制编写的程序代码要比那些杂乱地使用Object变量,然后再进行强制类型转换的代码具有更好的安全性和可读性。泛型对于集合类尤其有用,例如,ArrayList就是一个无处不在的集合类。
没有泛型前,集合类存放和取出都是Object,需要手动的进行强制类型转换。
一旦类型不匹配,转换将会报错。
实际的应用中,开发者往往会将同一类型的对象存放在集合中,没有泛型不仅对存放的对象没有约束,而且取出来还要进行类型转换,十分的不友好。
JDK5中引入了泛型的概念,泛型的本质是参数化类型,使得开发者可以利用集合更加方便的管理一群对象。
伪泛型
需要注意的是,Java中的泛型就是“伪泛型”。
Java只会在编译时对泛型进行校验,在运行时是没有“泛型”一说的。
通过ArrayList类来看看为什么Java是“伪泛型”,以及如何实现“泛型擦除”。
ArrayList
ArrayList集合应该是开发中用的最多的集合类了。
通常我们这么用:
ArrayList<String> names = new ArrayList<>();
names.add("admin");
names.add("root");
构建一个集合,用于存放姓名,姓名都是字符串类型的,所以使用String泛型。
使用泛型后,的确我们只能add一个字符串类型,否则编译器就会报错。
看起来近乎完美,那为什么说Java的“伪泛型”呢?
我们看一下ArrayList的源码。
add()
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
调用add()时,ArrayList将对象存放到了内部的elementData[]数组中。
elementData
transient Object[] elementData;
通过查看elementData我们发现,它是个 Object[] ,而非String[],这意味着它可以存放任意类型的对象。
Java只会在编译期间帮我们校验泛型,运行时没有“泛型”一说。
ArrayList<String>
底层依然使用Object[]来存放对象,当我们从names中获取元素时,Java会帮我们把Object自动的转换为String,只是转换的工作不用程序员自己去做,所以说Java的泛型是“伪泛型”。
泛型擦除
既然底层是Object[],就意味着我们可以往names中存放任意类型的对象。
涉及的知识点:反射。
直接上代码:
public static void main(String[] args) throws Exception {
ArrayList<String> names = new ArrayList<>();
names.add("admin");
names.add("root");
Method add = names.getClass().getMethod("add", Object.class);
add.invoke(names, 10);
add.invoke(names, 19.98);
add.invoke(names, Boolean.FALSE);
for (Object s : names) {
System.out.println(s.getClass().getSimpleName() + "\t:" + s);
}
}
输出如下:
String :admin
String :root
Integer :10
Double :19.98
Boolean :false
我们往一个String集合中,存放了String、Integer、Double以及Boolean类型。
尾巴
MyBatis中的泛型安全问题!!!
泛型的引入,使得程序员可以更加方便的使用集合管理对象,不得不说它是一个好的设计。
但由于是“伪泛型”,所以使用时还是需要注意,否则很有可能会报错:ClassCastException。
例如:使用MyBatis框架时,我们可能希望数据库返回给我们一个int集合,
我们一般会这么写:
List<Integer> getCount();
但是MyBatis将Sql的count()函数返回值使用Long类型存放,这个方法运行不会有什么问题,因为List<Integer>
是可以存放Long的,但是一旦将其取出来,转换为Integer就会报错:ClassCastException。
因为Long无法转换为Integer。