Skip to content

最近一直忙于编译原理的学习,难得空闲下来,重新巩固了一下泛型的知识。在此小记一下。

泛型程序设计意味着编写的代码可以被很多不同类型的对象重用

泛型类

在java中增加泛型类前,泛型程序设计时通过继承等方式实现的。例如:ArrayList维护一个Object数组的引用。 这种方式存在着两个问题:

  1. 当获取一个值时,必须进行强制类型转换。
  2. 没有进行任何检测,可以向其中添加任何类的对象。

因此,对于调用,编译和运行都不会出错。但在某些情况下进行了错误的强制类型转换使用。就会报错。

对此,泛型提供了更好的解决方案:类型参数。这不仅使得代码更具可读性,也使得代码更加安全。因为编译器可以根据这个信息推断出get时的类型,不需要进行强制类型转换。在编译期间检查出类型错误,而不是在运行时才检测出。

一个泛型类就是具有一个或多个类型变量的类。(使用大写形式,且比较短。在java中,E表示集合类型,K和V则是键值对,T表示任意类型)

泛型方法

泛型方法的类型变量放在修饰符后,返回值前面。泛型方法可以定义在普通类中,也可以定义在泛型类中。

当调用一个泛型方法时,在方法名前的尖括号中放入具体的类型。当然在大多数情况下,编译器可以推导出类型,意味着我们可以省去。(在某些情况下,编译器无法推导出,此时需要指明)

类型变量的限定

有时候类或方法需要对类型变量加以约束,< T extends Object > T

当做出这样的限定后,泛型的变量类型便被约束了。当然一个类型变量或通配符可以有多个限定,只需要用&隔开即可。< T extends Object1 & Object2 > T 值得注意的是,在java中可以根据需要拥有多个接口超类型,但限定中至多有一个类。如果用一个类作为限定,它必须位于限定列表的第一个

extends决定了泛型变量的上限。

泛型代码和虚拟机

虚拟机没有泛型类对象,所有对象都是属于普通对象。

  • 类型擦除:无论何时定义一个泛型类型,都自动提供了一个相应的原始类型。原始类型的名字就是删去类型参数后的泛型类型名。擦除类型变量,并替换为限定类型类型。(无限定的变量用Object)

  • 翻译泛型表达式:当程序调用泛型方法时,如果擦除返回类型,编译器会插入强制类型转换。

  • 翻译泛型方法:类型擦除也会出现在泛型方法中,只留下限定类型。但这可能会导致类型擦除和多态发生冲突。

约束与局限性

  • 不能用基本数据类型实例化类型参数,即不能有< double >,但可以有< Double >,原因是类型擦除。

  • 运行时类型查询只适用于原始类型,而不适用于泛型类型。当试图查询一个对象是否属于某个泛型类型时,倘若使用instanceof会得到一个编译器错误。同样的道理,getClass方法总是返回原始类型。

  • 不能创建参数化类型的数组,例如:Pair< String >[] table = new Pair< String > [10];类型擦除之后,table类型时Pair[],,可以把它转化为Object[],数组会记住其元素类型,如果试图存其他类型,则会报错。不过对于泛型,这种机制会使之无效。不过仍会导致一个类型错误。因此,不能创建参数化类型的数组。当然可以声明通配类型的数组,然后进行类型转换。

  • 不能构造泛型数组,因为数组本身也有类型,用来监控存在虚拟机中的数组。

  • 泛型类的静态上下文中类型变量无效:不能在静态域或方法中使用类型变量。

  • 不能抛出或捕获泛型类的实例:可以消除对受查异常的检查

  • 注意擦除后的冲突:要想支持擦除的转换,就需要强制限制一个类或类型变量不能同时成为两个接口类型的子类,而这两个接口时同一接口的不同参数。

泛型类型的继承规则

  • 无论S和T有什么关系,通常calssName < S > 和 calssName < T > 没有任何关系

待续...

Released under the MIT License.