J005-[Java菜鸟系列] 红警思维看"泛型"
菜鸟:如何理解 “ArrayList<E>” 中的 < E>?最近在学习 Java 编程,查看 Library 的时候,总是能看到以 结尾的参数。请问这是什么啊?具体如何使用?
美军的困境:多功能战车陷阱
在越南战争中,美军需要同时应对苏军航空兵/越共游击队,也需要承担坦克维护任务。
五角大楼的第一套方案是,订购三种战车,分别用于防空/对地攻击/维修;第二套方案是订购一种多功能战车。
第一种方案简单,但容易浪费,而且不灵活;最后决定用第二种方案,它可以根据实际情况改变用途。
多功能步兵战车但战场上却出现了坑队友事件:粗心的大兵把攻击战车用于坦克维护,导致坦克被击毁。
Error:坑队友不能忍!美利坚再有钱也经不起这么折腾。
大兵们想出了一个办法,在多功能战车用于坦克维护前,让工程师进入战车。这样就不会操作失误了。
安全操作Java的困境:Object类型陷阱
背景:ArrayList的实际需求
在Java中我们用ArrayList存储对象,因为它非常的方便。这就相当于美国大兵的战车。
为了满足的对象存储需求,有两种方案。
第一种:为每个对象设计一个不同的ArrayList类。第二:设计通用ArrayList类。
因为对象的种类多种多样,怎么可能每一个都设计一个相同的ArrayList呢?
所以,Java的设计者和美国大兵一样,选择了"通用方案"。
坑队友:通用方案的弊端
如何通用?最简单办法就是用Object
来设计ArrayList。
Object是根类,可以容纳一切对象,所以"通用化"就实现了。
//示例-J005-1:最初的设计
public class ArrayList {
private Object[] elementData;
public Object get(int i) { . . . }
public void add(Object o) { . . . }
}
但是,这种方法同样会造成"坑队友"。
假设我们要使用ArrayList装String对象(stringList),当我们获取值的时候,其实获取的东西是Object,所以我们必须手动转为String。(强制转型)
String s = (String) stringList.get(0);
当你满怀信心的运行的时候,结果却会是这样的
Exception in thread "main" java.lang.ClassCastException: java.lang.StringBuffer cannot be cast to java.lang.String
???
为什么会出现这种情况呢?
因为,向stringList中添加元素的时候,使用的是public void add(Object o) { . . . }
方法,实际上你可以往里面添加任何东西。
所以前面你不小心添加了一个StringBuffer,呃,就这么直接挂掉了。
遇到这种事情的心情,emmm.... 你再看看这张图就懂了。
Error:坑队友 内心崩溃像美国大兵学习
这件事情上,我们需要向美国大兵学习,先回顾一下美国大兵是怎么做的。
大兵们在使用多功能战车的时候,要求专业人士先进入,以确保战车的安全。
这样答案就很明确了,在实例化ArrayList之前,我们先传入一个"特殊参数",以确保安全就行了。
安全操作我们该怎么样传入这个参数呢?
仔细想一想,虽然{}
和()
都被占了,好在还剩下一个框框<>
没用,那么就用这个了。
这个参数就被称为"类型参数"。
在设计泛型类的时候,我们用T
来指代还未被确定的类型。
当然你也可以用其他的字母,这只是一个习惯。
<T>代表Type,代指一切类型。其实在ArrayList等 集合类 中一般会使用<E>,也就是Element的意思。
//示例-J005-2:改进后的设计
public class ArrayList<T> {
private T[] elementData;
public T get(int i) { . . . }
public void add(T o) { . . . }
}
只要我们在实例化之前,先指定其类型,就可以避免"坑队友"事件的发生了。
image为什么new ArrayList<>中的泛型参数可以被省略呢?
构造对象之后,对象被传递给ArrayList<String>
类型的变量,那么构造器的泛型参数自然也就是String。
在Java SE 7 之后,如果泛型参数可被推断,那么就可省略。
总结:泛型在效率和安全之间找到了平衡
- 泛型诞生的背景还是懒。诸如ArrayList这样的类,并不需要为每个对象都设计一个,完全是可以通用的。
-
通用设计产生了安全性问题。一开始人们用Object类型建立ArrayList类,但是这种方法很容易"坑队友",造成
ClassCastException
。使用起来非常不安全。 - 在安全和效率之间找到了平衡。为了在通用的基础上保证安全性,增加了一个新的参数,叫做"类型参数"。
可以说,"泛型"的本质就是新增了一个叫做"类型参数"<>
的东西。
顺便提一句"兼容性"
泛型是Java1.5才加入的,不可避免的有"兼容性"问题。
为了向后兼容,"泛型"只存在于"编译器",不存在于"虚拟机"。
所以,只要程序运行起来,就没有"泛型"这个概念了,这被称为"类型擦除"。
也有人把这称为"Java的伪泛型",我们就不用管了,一个名字而已,不用争来争去的。
"类型擦除"后,泛型类露出其本来面目了,这个本来面目就叫做"原始类型"。
如果你什么都没动的话,比如你设计的类是<T>
泛型类型,那么本来面目就是Object,但是你也可以限定一下,比如<T extends Comparable>
,那么本来面目就是Comparable。
其实,了解了"泛型"产生的历史背景和其本质,这些细节都好理解。
End
鲁迅镇楼
鲁迅**心如止水是Java/AHK持续学习者,欢迎您来和我探讨Java/AHK问题 _ **
更多文章:
版权声明:
该文章版权系“心如止水”所有,欢迎分享、转发,但如需转载,请联系QQ:3404624865,得到许可并标明出处和原链接后方可转载。未经授权,禁止转载。版权所有 ©心如止水 保留一切权利。
心如止水
网友评论