“让错误尽量在编译被发现”
“你必须知道边界所在,才能成为高手”
---《Thinking in Java》
错误在编译时被发现是十分让人向往的,Java泛型在JDK1.5中的引入目的就是“让编译器承担更多的工作,保证类型的正确”。但是随之而来的是开发社区褒贬不一的观点,就像在异常中说的那样----Java的泛型和异常一样,备受人们争议。
Java泛型带来的优秀产物就是集合框架,相比于以前使用Java集合框架,使用泛型后的集合框架显得十分简洁和安全。随之而来的便是Java泛型给人们带来的种种疑惑,设计者曾说Java泛型的主要设计灵感是来自C++的模板,但是稍微知道C++的人也许都会对Java的泛型感到失望,如何优雅地使用Java泛型进行优秀的程序设计是值得探索的。
1.Java的泛型为什么会这样?
Java的泛型是在Java出现几乎10年后才出现的特性,而此时已经存在大量的旧代码,作为一门广泛使用的生产语言,Java不得不考虑兼容性。为了让使用新特性的人能够逐步的迁移到新的平台,新代码必须和旧代码保持兼容,这是一个十分伟大的动机,不会在一夜之间破坏现有的所有代码!所以Java的设计者们在一个月黑风高的晚上决定使用“擦除机制”来设计泛型!
2.擦除机制
正确理解泛型概念的首要前提是理解擦除机制(type erasure)。Java中的泛型基本上都在编译器这个层次上来实现的。在生成的Java字节代码中是不包含泛型中的类型信息(在运行时时候都是原生类型(raw type))。在编写泛型代码的时的任何类型信息都会被编译器在编译的时候去掉。这个过程称为“类型擦除”。在代码中如:List
擦除的整个过程也比较容易理解:首先找到用来替换类型参数的具体类。这个具体类不指名则默认Object,如果指定了参数类型的上界,那么就使用这个上界来替换类型参数。同时去掉类型声明,即去掉<>的内容,比如T get()方法声明就编程了Object get(),List
3.不可具体化的类型
不可具体化的类型是指:运行时表示法包含的信息比它的编译时表示法包含的信息更少的类型。泛型是典型的不可具体化类型,参考ArrayList
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
@SuppressWarnings("unchecked")
EelementData(int index) {
return (E) elementData[index];
}
运行时实际持有Object数组,但是编译的时候是可以有效的判断放入的是否是String类型,从而避免ClassCastException异常。
与之相反的便是”可具体化的类型“,数组便是一个例子。数组总是在运行时才检查他们元素的类型约束。比如
Object[]father = new Father[10];
father[0]= new Son();
father[1]= new Integer(100);
这里并没有编译错误,但是运行时会抛出ArrayStoreException异常,和泛型的特点相反。比如如下大家熟悉的代码:
List