J005-[Java菜鸟系列] 红警思维看"泛型"菜鸟:如何理解 “ArrayList<E>” 中的 < E>?最近在学习 Java 编程,查看 Library 的时候,总是能看到以 结尾的参数。请问这是什么啊?具体如何使用?美军的困境:多功能战车陷阱在越南战争中,美军需要同时应对苏军航空兵/越共游击队,也需要承担坦克维护任务。五角大楼的第一套方案是,订购三种战车,分别用于防空/对地攻击/维修;第二套方案是订购一种多功能战车。第一种方案简单,但容易浪费,而且不灵活;最后决定用第二种方案,它可以根据实际情况改变用途。但战场上却出现了坑队友事件:粗心的大兵把攻击战车用于坦克维护,导致坦克被击毁。不能忍!美利坚再有钱也经不起这么折腾。大兵们想出了一个办法,在多功能战车用于坦克维护前,让工程师进入战车。这样就不会操作失误了。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…. 你再看看这张图就懂了。像美国大兵学习这件事情上,我们需要向美国大兵学习,先回顾一下美国大兵是怎么做的。大兵们在使用多功能战车的时候,要求专业人士先进入,以确保战车的安全。这样答案就很明确了,在实例化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) { . . . } }只要我们在实例化之前,先指定其类型,就可以避免"坑队友"事件的发生了。为什么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问题 ^_^ 更多文章:[Java菜鸟系列] 内部类与lambda表达式[Java菜鸟系列] 协议与回调版权声明:该文章版权系“心如止水”所有,欢迎分享、转发,但如需转载,请联系QQ:3404624865,得到许可并标明出处和原链接后方可转载。未经授权,禁止转载。版权所有 ©心如止水 保留一切权利。