在Java的早期,类成员或方法使用的类型一般是固定的,这种情况往往是程序变得僵硬起来,要想为其他的类型实现相同的功能那么就要重新写一份,当然你也可以使用多态的机制让你的代码更加灵活,可是多态毕竟也是有限制的。例如早期的容器的设计,采用了Object的方式来保存所有类型的对象,如果想要从容器中取出你先前放进去的类型的对象,除了强转,也没有什么别的方法了,而且强转还会带来很多问题,因为你向这个Object的容器放入对象的时候并没有限制,所以ClassCastException问题就会伴随着你的程序。
Java5后引入了泛型,这将使得所有基于固定类型的代码将可以被得到复用,最典型的便是容器,现在你向创建一个装Integer的容器,它会强迫你只能添加Integer的数据,如果你试图添加一个String对象,那么你会得到一个编译时错误。
Java中的泛型真的那么好用吗?
貌似我们在使用泛型的时候感受到了它的便利,但有的时候我们想当然的认为泛型可以帮助我们解决下列问题:
class GenericType {
void doSomething() {
//omit
}
}
public class GenericTypeTest<T> {
public GenericTypeTest(T t) {
t.doSomething(); //编译时错误
}
public static void main(String[] args) throws Exception{
new GenericTypeTest(new GenericType());
}
}
究竟发生了什么呢?
说到这里就不得不说一下Java中的泛型处理机制。和很多语言不同(如C++、Pythong)在使用泛型的时候会去检查该泛型对应的具体类型的是否存在着你使用的该方法,如果没有该方法,那么编译器会报错。而Java则做不到,泛型只存在于编译时的静态检查间,检查完成所有的泛型在没有边界的情况下将被擦除为Object类型,也可以通过类似<? extends Comparable>这种方法来指定边界,指定之后会被擦除为Comparable。所以JVM并不知道该对象具有doSomething()的方法,所以会报错。
解决以上问题:
给泛型T指定边界
public class GenericTypeTest<? extends GenericType> {
public GenericTypeTest(T t) {
t.doSomething(); //编译时错误
}
public static void main(String[] args) throws Exception{
new GenericTypeTest(new GenericType());
}
}
既然Java采用了擦除这种方式来实现泛型,那么他是怎样通过泛型获取真实类型的呢。实际上在擦除之前,编译器就已经在泛型的前面加入了强制转换。
总结:
之所以Java会以擦除方式来实现泛型,是因为Java的遗留问题,Java泛型是后来才加入进来的,为了支持更低版本的Java代码,就必须这样做,试想以下代码
//Java5后
List<T> list = new ArrayList<T>();
//Java5以前
List list = new ArrayList();
如果不使用擦除手段,那么这两个list将不会是一样的。但事实情况下却是:
public static void main(String[] args) throws Exception{
List<String> list = new ArrayList<String>();
List list2 = new ArrayList();
System.out.println(list.getClass() == list2.getClass()); // true
}
所以只有这种方式才会使得现有代码不会和之前没有泛型的代码发生冲突。
网友评论