共计 2456 个字符,预计需要花费 7 分钟才能阅读完成。
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,得到许可并标明出处和原链接后方可转载。未经授权,禁止转载。版权所有 ©心如止水 保留一切权利。