引言
Java中存在各式各样的语法糖,包括泛型、自动装箱/拆箱、遍历循环、变长参数、枚举、支持枚举和字符串的switch语句、带资源的try语句等,其中泛型的引入弥补了Java原有类型系统的不足,同时提升了Java语言的开发效率,是一颗大大的“糖丸”。为了帮助狼猿们彻底弄懂泛型,推荐几篇资料:
- 《Effective Java中文版》
- 一篇写得很好的博客(待补充)
- 知乎问答
Java中的泛型是语法糖
泛型是JDK 1.5引入的一项新特性,它的本质是参数化类型。在C#和Java中都有泛型技术的应用,但前者使用类型膨胀的方式实现泛型,称为“真实泛型”,后者使用类型擦除的方式实现泛型,称为“伪泛型”。对于伪泛型而言,泛型就是颗“语法糖”,其完全由Java编译器实现,在生成的字节码中已经不存在泛型,仍然是原始类型(raw type)。
为何当初JDK设计团队选择类型擦除的方式实现泛型呢?或许是因为实现简单、或许是考虑兼容性等。在使用过Java泛型后,会很容易发现其优势和不足。
泛型的优势与不足
关于Java泛型,其优势可归为如下几点:
- 代码复用
- 写入时自动类型检查、访问时自动转型
- 可读性好
其中,第2点也称为“编译时类型安全”,这是相对于“运行时类型安全”而言,前者拥有更高的开发效率。
Every coin has two sides. Java泛型也存在不足:
- 方法重载语义混乱
如下两个方法签名是无法重载成功的,这会给很多刚入门的Java萌新带来困惑。
签名1:method(List<String>)
签名2:method(List<Integer>)
鉴于泛型有如此多的优势,最佳实践:
编写代码时不要再使用原始类型,全部使用泛型。
该最佳实践有2个例外:
- 类字面量中只能使用原始类型
- instanceof运算符使用原始类型
关于instanceof运算符的习惯用法:
if (o instanceof Set) {
Set<?> m = (Set<?>) o;
...
}
此处的强制类型转换是个经编译器检查过的(checked)转换,不会产生编译器警告。
泛型的父子类型体系
Java语言是一门面向对象的程序设计语言,自然实现了面向对象的3大特性:封装、继承、多态。在引入泛型之前,Java的原始类型存在一套父子类型体系,该体系决定了“哪些类型的对象引用可以赋值给哪些类型的对象”,比如Integer是Integer的子类型、Integer是Number的子类型,又比如Integer[]是Object[]的子类型。那引入泛型后,List<Integer>是List<Number>的子类型么?答案是:No!
那泛型中的父子类型关系如何表达呢?使用通配符。如下所示:
List<? super Integer> 是 List<Integer>的父类型
List<Number> 是 List<? extends Number>的父类型
由于类型擦除的原因,泛型的父子类型体系完全是由编译器javac实现的。
泛型与数组
上面提到Integer[]是Object[]的子类型、而List<Integer>却不是List<Number>的子类型,这是泛型设计者实施的规则。为何要这样设计?肯定是因为“Integer[]是Object[]的子类型”规则存在缺陷。我们把数组的这种子类型规则表述为“数组是协变的”,从而“参数化类型是非协变的”。
除了父子类型体系的区别,还存在另一个区别:数组类型是可具体化的,而泛型是不可具体化的。如E、List<E>、List<String>等都是不可具体化的,唯一可具体化的泛型是无限制通配符?,如List<?>、Map<?,?>。
不可具体化:运行时表示法包含的信息比编译时表示法包含的信息少。
由于上述2个原因,泛型与数组不能很好地混用。因此,如下数组创建表达式是非法的:
new List<E>[]
new List<String>[]
new E[]
一般地,数组元素的类型不能是不可具体化的,使用变长参数时要注意。
既然无法创建不可具体化类型的数组,那底层使用数组的集合容器泛型如何实现呢?两种方式:
- 使用Object[]作为底层数组,强转为E[],并@SuppressWarnings(“unchecked”)
- 使用Object[]作为底层数组时,访问它时对元素类型做强转,并@SuppressWarnings(“unchecked”)
通配符
- instanceof运算符可以使用无限制通配符,但显得多余
- 当类型参数和通配符均可满足要求时,优先使用通配符
- 使用通配符时,遵守PECS原则
- 不要在返回类型中使用通配符
- 可以使用类型参数捕获通配符
- 所有的Comparable、Comparator都是消费者
结语
掌握了泛型的理论要点后,剩下的就是看JDK源码,然后在实践中编写自己的泛型和泛型方法。