Java泛型要点

在这里插入图片描述

引言

Java中存在各式各样的语法糖,包括泛型、自动装箱/拆箱、遍历循环、变长参数、枚举、支持枚举和字符串的switch语句、带资源的try语句等,其中泛型的引入弥补了Java原有类型系统的不足,同时提升了Java语言的开发效率,是一颗大大的“糖丸”。为了帮助狼猿们彻底弄懂泛型,推荐几篇资料:

  1. 《Effective Java中文版》
  2. 一篇写得很好的博客(待补充)
  3. 知乎问答

Java中的泛型是语法糖

泛型是JDK 1.5引入的一项新特性,它的本质是参数化类型。在C#和Java中都有泛型技术的应用,但前者使用类型膨胀的方式实现泛型,称为“真实泛型”,后者使用类型擦除的方式实现泛型,称为“伪泛型”。对于伪泛型而言,泛型就是颗“语法糖”,其完全由Java编译器实现,在生成的字节码中已经不存在泛型,仍然是原始类型(raw type)。

为何当初JDK设计团队选择类型擦除的方式实现泛型呢?或许是因为实现简单、或许是考虑兼容性等。在使用过Java泛型后,会很容易发现其优势和不足。

泛型的优势与不足

关于Java泛型,其优势可归为如下几点:

  1. 代码复用
  2. 写入时自动类型检查、访问时自动转型
  3. 可读性好

其中,第2点也称为“编译时类型安全”,这是相对于“运行时类型安全”而言,前者拥有更高的开发效率。

Every coin has two sides. Java泛型也存在不足:

  • 方法重载语义混乱

如下两个方法签名是无法重载成功的,这会给很多刚入门的Java萌新带来困惑。

签名1method(List<String>)
签名2method(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”)

通配符

  1. instanceof运算符可以使用无限制通配符,但显得多余
  2. 当类型参数和通配符均可满足要求时,优先使用通配符
  3. 使用通配符时,遵守PECS原则
  4. 不要在返回类型中使用通配符
  5. 可以使用类型参数捕获通配符
  6. 所有的Comparable、Comparator都是消费者

结语

掌握了泛型的理论要点后,剩下的就是看JDK源码,然后在实践中编写自己的泛型和泛型方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值