目录
泛型正是我们需要的程序设计手段。使用泛型机制编写的程序代码要比那些杂乱地使用 Object 变量,然后再进行强制类型转换的代码具有更好的安全性和可读性。泛型对于集合类尤其有用,例如ArrayList。
1、为什么要使用泛型程序设计
泛型程序设计(Generic programing)意味着编写的代码可以被很多不同类型的对象所重用。
下面来研究泛型程序设计的机制是如何演变的,以及这对于用户和实现者来说意味着什么
类型参数的好处
类型参数(type parameters)用来指示元素类型,如:ArrayList<String> files = new ArrayList<String>();(SE 7 及以后的版本可在构造函数中省略泛型类型,如:ArrayList<String> files = new ArrayList<>(); )
使用类型参数,一来不用再强制转换对象类型(原来全为Object,需要强制转换);二来避免了一个数组列表存储不同类型对象带来的错误,更加安全;三来提高了代码可读性。
谁想成为泛型程序猿
使用一个像 ArrayList 这样一个泛型类很容易,但是实现一个泛型类就不是那么简单了。
对于类型参数,程序员可能想要内置(plug in)所有的类(即希望做所有的事情,预测所用类的未来可能有的所有用途),这一任务非常难。例如:ArrayList 类有一个 addAll 方法用来添加另一个集合的所有元素。对于将 ArrayList<Manager> 全部添加到 ArrayList<Employee> 中是可行的,但是反过来调用就不行了。这种只能允许前一个调用,不允许后一个调用的情况,Java语言的设计者发明了一个具有独创性的新概念:通配符类型(wildcard type),它解决了这个问题。通配符类型非常抽象,但能让库的构建者编写出尽可能灵活的方法
应用程序员很可能不喜欢编写太多的范型代码。JDK 开发人员已经做出了很大的努力,为所有的集合类提供了类型参数。
本章介绍实现自己的范型代码需要了解的各种知识。
2、定义简单泛型类
一个泛型类(generic class)就是具有一个或多个类型变量的类。
本章以 Pair 类作为例子:
public class Pair<T> {
private T first;
private T second;
public Pair() { first = null; second = null; }
public Pair(T first, T second) { this.first = first; this.second = second; }
public T getFirst() { return first; }
public T getSecond() { return second; }
public void setFirst(T newValue) { first = newValue; }
public void setSecond(T newValue) { second = newValue; }
}
泛型类的类型变量可以有多个,如:Pair<T, U>;
类定义中的类型变量制定了方法的返回类型以及域和局部变量的类型。如:private T first;
用具体的类型替代类型变量就可以实例化泛型类,如:new Pair<String>;
泛型类可看作普通类的工厂。
3、范型方法
除了定义泛型类,还可以定义一个带有类型参数的简单方法:
class ArrayAlg {
public static <T> T getMiddle(T... a) {
return a[
a.length / 2];
}
}
这个方法定义在一个普通类中。注意:类型变量放在修饰符(这里是 public static)的后面,返回类型的前面;
范型方法可以定义在普通类中,也可定义在泛型类中;
在调用一个泛型方法时,在方法名前的尖括号中放入具体的类型:String middle = ArrayAlg.<String>getMiddle("John", "Q.");
4、类型变量的限定
对于以下代码:
class ArrayAlg {
public static <T> T min(T[] a) {
if (a == null || a.length == 0) return null;
T smallest = a[0];
for(int i = 0; i < a.length; i++)
if (smallest.compareTo(a[i]) > 0) smallest = a[I];
return smallest;
}
}
如何才能确定对象 smallest 的类型 T 所属的类有 compareTo 方法呢?解决这个问题,可以限定 T 为实现了 Comparable 接口的类,通过对类型变量 T 设置限定(bound)实现这一点:public static <T extends Comparable> T min(T[] a) . . .;
这样,泛型的 min 方法就只能由实现了 Comparable 接口的类对象进行嗲用,如 String、LocalDate 等。
为什么使用 <T extends ...> 而不是 implements?这个记法:<T extends BoundingType> 表示T应该是绑定类型的子类型(subtype)。T 和绑定类型可以是类也可以是接口。选择 extends 是其更接近子类的概念。
一个类型变量或通配符可以有多个限定,如:T extends Comparable & Serializable。限定类型用“&”分隔,类型变量用“,“分隔。限定类型可以有多个超接口,但只能有一个类,且这个类必须放在限定类表的第一个。
5、范型代码和虚拟机
类型擦除
定义的一个范型类型,都会自动提供一个相应的原始类型(raw type)。
原始类型的名字是删去类型参数后的范型类型名;擦除(erased)类型变量,并替换为限定类型(若无则是Object)。如:
Pair<T> 的原始类型是:
//范型类型(generic type)
public class Pair<T> {
private T first;
private T second;
public Pair() { first = null; second = null; }
public Pair(T first, T second) { this.first = first; this.second = second; }
public T getFirst() { return first; }
public T getSecond() { return second; }
public void setFirst(T newValue) { first = newValue; }
public void setSecond(T newValue) { second = newValue; }
}
//原始类型(raw type)
public class Pair {
private Object first;
private Object second;
public Pair() { first = null; second = null; }
public Pair(Object first, Object second) { this.first = first; this.second = second; }
public Object getFirst() { return first; }
public Object getSecond() { return second; }
public void setFirst(Object newValue) { first = newValue; }
public void setSecond(Object newValue) { second = newValue; }
}
原始类型用第一个限定的类型变量来替换。
需要记住有关 Java 泛型转换的事实:
1、虚拟机中没有泛型,只有普通的类和方法
2、所有的类型参数都用他们的限定类型替换
3、桥方法被合成来保持多态
4、为保持类型安全性,必要时插入强制类型转换
6、约束与局限性
大多数限制都是由类型擦除引起的
1、不能用基本类型实例化类型参数
2、运行时类型查询只适用于原始类型
3、不能创建参数化类型的数组
4、Varargs 警告
5、不能实例化类型变量
6、不能构造泛型数组
7、泛型类的静态上下文中类型变量无效(禁止使用带有类型变量的静态域和方法)
8、不能抛出或捕获泛型类的实例
9、可以消除对受查异常的检查
10、注意擦除后的冲突
7、泛型类型的继承规则
具有继承关系的两个类,在作为泛型类型的类型变量时,两者是没有关系的。如 Employee 被 Manager 继承,但是 Pair(Imployee) 和 Pair(Manager) 是没有关系的。
永远可以将参数化类型转换为一个原始类型。例如:Pair<Employee>是原始类型Pair的一个字类型。