对Java泛型的一些思考

原创博文,转载请注明出处,谢谢。

最近研究了一下Java的泛型,在此总结了一些不太容易理解,或者是以前所一知半解的地方,供大家参考,有什么不正确的地方,还请多多指教。微笑(PS:《Effective Java》真是一本好书,值得推荐)

首先,Java为何需要泛型?或者说,泛型解决了一个什么问题?要明确一点,泛型是JDK 1.5才开始引入的。举一个简单的例子,在以前(JDK 1.5之前)使用ArrayList这个集合,存取的方法(比如:add、get)的参数以及返回值都是Object,这不仅导致了我们调其中的方法的时候,需要做大量的强制类型转换,还很容易导致程序运行异常(比如:ClassCastException、ArrayStroeException),因而程序的健壮性不高。引入泛型之后,虽然我们依旧可以使用非泛型的版本(JDK为了保证向前兼容),但是泛型版本的集合框架给我们带来的好处是毋庸置疑的。比如:要使用泛型的ArrayList,仅需这样声明:ArrayList<String> list = xxx(xxx表示省略),这样就表示list是一个指向String类型的ArrayList,此时,利用list引用调ArrayList的方法的时候,存取数据的方法参数和返回值就不再是Object了,这不仅帮我们省下了很多强制类型转换的代码,也极大的降低了程序运行时抛出异常的几率。

其次,Java的泛型是怎么实现的?它和C++的模板有何区别?Java的泛型是一种运行时擦除的技术。也就是说,在运行时,所有泛型信息都会丢失,比如:ArrayList<String> list在运行的时候,JVM已经不记得这个list曾经是声明为String泛型类型的了。但是在编译的阶段,泛型信息是不会丢失的。也就是说,声明ArrayList<String> list,存取数据的方法参数及返回值为String,其实是编译器帮我们处理了,因此也方便我们在编译阶段就发现错误。然而,C++的模板,则是由C++编译器帮我们编译出不同的类以及函数。打比方,C++的vector<int>、vector<double>,在运行时其实是不同的两个类了。而Java的ArrayList<Integer>、ArrayList<Double>,在运行时,都是同一个类,因为它们在运行时,泛型信息Integer、Double就会丢失。

然而,有一个现象,Object[] objs = new String[10],这样的写法是可以通过编译的,但是ArrayList<Object> list = new ArrayList<String>就是不行的。为什么呢?因为这样不安全。对于数组来说,数组是协变的,也就是说,如果B是A的子类,那么,B[]也一定是A[]的子类。协变增加了灵活性,但是却引入一些风险。比如:

String[] strs = new String[10];
Comparable[] comps = strs;
comps[0] = 2;
这样的代码在编译阶段是没问题的,但是在运行阶段,就会抛出ArrayStoreException。假如我们用泛型集合替换数组:

ArrayList<String> list = new ArrayList<String>();
ArrayList<Comparable> list2 = list;
list2.add(2);
这样的代码,连编译都编译不过,别说运行了。所以,Java这个特性是有利于我们在编译阶段发现错误的。但是,这个特性也会引起我们的一些不便。比如,我们就想声明一个可以同时指向ArrayList<Integer>和ArrayList<Double>的list引用,怎么办?此时,就可以用上泛型的通配符了。泛型的通配符一般有3种,分别为?、? extends xxx和? super xxx。如果想 声明一个可以同时指向ArrayList<Integer>和ArrayList<Double>的list引用,用ArrayList<?> list和ArrayList<? extends Comparable>都是可以的。这三种通配符,有何区别呢?使用?可以的list引用可以指向任意泛型类型的ArrayList,而? extends xxx的必须是泛型类型为xxx或xxx的子类的ArrayList,而? super xxx则必须是泛型类型为xxx或xxx的父类的ArrayList。什么场景下应该使用哪种通配符呢?在《Effective Java》里面,有一条原则:Producer Extend, Consumer Super,简称PECS。也就是说,是producer的话,用? extends xxx,是consumer,则用? super xxx。可是,到底什么是producer,什么是consumer呢?其实很简单,对于一个方法来说,如果它的参数列表带有泛型参数,它就是consumer;如果它的返回值是泛型参数,它就是producer;如果两者都有,那就既是producer,又是consumer;两者都没有,就都不是。因此,用ArrayList<? extends xxx>,那些方法参数带有泛型参数的,一般就不要调;使用ArrayList<? super xxx>,一般就不要调返回值是泛型参数的;用ArrayList<?>,无论是方法参数带泛型参数的方法,还是返回值带泛型参数的方法,都不要调。

最后,为何创建不了泛型类的数组呢?其实也是因为不安全。比如ArrayList<String>[] array = new ArrayList<String>[10]是编译不过的。假如这行代码可以编译通过,那么就有可能在运行时引发ArrayStoreException。比如:

ArrayList<String>[] array = new ArrayList<String>[10]; //假设可以编译通过
Object[] objs  = array;
objs[0] = 1;


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值