谈谈java泛型

j ava1.5引入了不少新特性,其中泛型就是一大亮点。记得曾经刚刚从书上了解到泛型这个概念时,觉得特别牛X。超强的归纳性,一个T就把各种类通通涵盖了,不由的从继承的角度大肆YY泛型的应用。可是YY来YY去还真找不着啥应用场景。最后实在无奈,也就用泛型写了个简单的stack,要不就造着书本上的例子依葫芦画瓢写几个例子。随着对java的进一步了解,对泛型也慢慢有了更深的了解。
在了解泛型前,首先要了解1.5为什么要追加泛型这个特性,而且极力推荐,官网上的解释:泛型的引入主要是为了提供强大的类型检查,消除1.4中没有泛型时的各种强制转换。好吧,它压根就和继承擦不上边。你可以很容易的在各种资料中找到类似List<A>并不继承于List<Object>的说法。但是泛型中还是提供了一些类似继承的概念。通配符(wildcard)这个概念很早的时候在书里看到的,当时一瞬间就按英文中式翻译法给他下了定义"野生的".至今我还是这么理解的,所以你可以很容易的将他和那些"家养的"区分开来,总之他是个特殊的存在你可以通过List<A> instanceof List<?>得到true的结果,可以理解成我有一个List,但我不知道他是什么类型的所以你只可以从List<?>中取数据,但是不能插入新的数据,因为你也不知道List中存的是什么类型的数据,允许随意插入可能导致类型转换异常。例如下面的代码会出现编译时错误。
<span style="font-size:14px;">void doSomething(List<?> list){
	list.add(list.get(0));
}</span>

很怪异的问题,从同一个list里头取出来的数据,居然不能插入到原list中。但是java编译器就是这么校验的。这是个问题吗?我认为不是,泛型的新特性中同样提供的解决案。如果你这么写就可以成功的通过编译器检查
<span style="font-size:14px;">void doSomething(List<?> list){
	addList(list);
}


<E> void addList(List<E> list){
	list.add(list.get(0));
}
</span>

你可能会问,这么写太复杂了,像下面这么写也是可以解决问题的
<span style="font-size:14px;"><E> void doSomething(List<E> list){
	list.add(list.get(0));
}</span>

是这样的这两种写法都是没有问题的,不过List<?>在语义上的表现更加明确一些。当然?还是有其存在价值的比如说你可以声明一个泛型数组List<?>[],这也是泛型数组唯一可以声明的方式了,想List<Object>[]这样的数组是不可以被声明的。Why?问题就出在List<Object>[]破坏了泛型引入的类型安全性。
<span style="font-size:14px;">List<A>[] array=new List<A>[9];
List<Object> object=new ArrayList<Object>();
Object[] objects=array;
objects[0]=object;
array[0].add(new A());</span>

于是List<Object>就轻松的逃过的编译器的类型检查,但是如果用List<?>[]的话就没有什么问题,因为你无法向里头插入数据。像这类用法可能不常见,但是扩展下的话类似List<Set<?>>,doSomething(Set<?> list)操作方法就是不可避免的。

java的泛型是通过类型擦除来完成的,这导致了不少争议。毕竟每一种方案都有好的一面和坏的一面,就我个人认为,java对泛型的实现真的是一个很棒的设计。先来说说类型擦除的缺点吧。类型擦除带来的最大弊端就是泛型的信息在运行时是不存在的。无法获得运行时的类型导致很多正常的逻辑复杂化的实现,比如我需要对某个泛型方法中的某些类型做操作,比如,你想优化一下ArrayList<Integer>,让内置数组设置为int[],但是由于类型擦除,类本身是无法知道自己所引用的的类型,你必须告诉类有关泛型的信息。类似下面的代码。
<span style="font-size:14px;">class ArrayList<E> {
	public ArrayList(Class<E> type) {
		if(type.equals(Integer.class)){
			store=new int[16];
		}
	}
}</span>

然后你就可以new ArrayList<Integer>(Integer.class),看起来比较坑,明明已经告诉A<Integer>了,还得在附带个Integer.class.不过还有个比较偏门的方法可以达到更好的效果,事实上在某些情况下泛型信息是可以被保存到运行时的。但是在什么情况下泛型信息可以被保留呢?那就是在一个class可以知道自己具体的泛型时,这些信息就可以被保留到运行环境中,但是class有可能在各种情况下调用,存在知道自己只会被具体泛型调用的情况吗?有!那就是匿名类。编译器会生成关于匿名类的class文件,匿名类的全部信息就可以被完整的带到运行环境java在这里还是保留了一个入口,不过想想也还是挺合理的。下面就提供下另一种实现方案。
<span style="font-size:14px;">public class MyList<E> {
	Object store;
	Type type;
	public MyList() {
		type=((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
		if(type.equals(Integer.class)){
			store=new int[16];
		}else{
			store=new Object[16];
		}
	}
	
	public void add(E e){
		if(type.equals(Integer.class)){
			((int[])store)[0]=(Integer)e;
		}
	}
}</span>

这样就可以声明一个匿名类MyList<Integer> list=new MyList<Integer>(){},这个类使用的是int[]为存储,以上粗略的给出了实现方法,不过实现的简单除暴了点。具体要实现起来就复杂了,有兴趣的朋友可以尝试一下。

下面来说说泛型类型擦除的优点吧,个人觉得类型还是很不错的,基本上实现了正常需求,此外类型擦除将泛型的实现和虚拟机进行了隔离(编译器级别),而且还保留了泛型信息(编译器知道泛型),攻守兼备。java7发布之后,越来越证明了这一实现方案的优越性。越来越多的其他语言尝试着按照jvm的规范来实现java虚拟机,来实现语言的跨平台性,正是因为泛型擦除,其他语言(可能有泛型,有更棒的泛型实现方案或者没泛型)不需要去关注本身语言与jvm的兼容性,假设泛型的实现时在虚拟机层级的。这为java从一门语言向一个语言的平台的转型提供了可能。而且java并没有抛弃泛型所拥有的信息,对未来如果需要提供jvm级的泛型时保留了很大的空间。每想到此都为设计者的远见所惊叹。哈哈,于是就情不自禁的喜欢上java的泛型了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值