9.2 深入泛型 9.2.1 定义泛型接口 泛型类

9.2 深入泛型

所谓泛型,就是允许在定义类、接口、方法时使用类型形参,这个类型形参(或叫泛型)将在声明变量、创建对象、调用方法时动态地指定(即传入实际的类型参数,也可称为类型实参)。

Java5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参,这就是在前面程序中看到的List<String>ArrayList<String>两种类型。

9.2.1 定义泛型接口、类

下面是Java5改写后List接口、 Iterator接口、Map的代码片段。

1
2
3
4
5
6
7
8
9
......
// 定义接口时指定了一个泛型形参,该形参名为E
public interface List<E> extends Collection<E> {
......
// 在该接口里,E可作为类型,使用下面方法可以使用E作为参数类型
Iterator<E> iterator();
......
boolean add(E e); //1
.........
1
2
3
4
5
6
7
8
9
......
// 定义接口时指定了一个泛型形参,该形参名为E
public interface Iterator<E> {

// 在该接口里E完全可以作为类型使用
E next();
......
boolean hasNext();
}
1
2
3
4
5
6
7
8
9
......
public interface Map<K, V> {
......
// 在该接口里K、V完全可以作为类型使用
//2
V put(K key, V value);
......
Set<K> keySet();
......

上面三个接口声明是比较简单的,除了尖括号中的内容—这就是泛型的实质:
允许在定义接口、类时声明泛型形参,泛型形参在整个接口、类体内可当成类型使用,几乎所有可使用普通类型的地方都可以使用这种泛型形参

除此之外,1、2处方法声明返回值类型是Iterator<E>Set<K>,这表明Set<K>形式是一种特殊的数据类型,是一种与Set不同的数据类型:可以认为Set<K>Set类型的子类。

例如使用List类型时,如果为E形参传入String类型实参,则产生了一个新的类型: List<String>类型,可以把List<String>想象成E被全部替换成String的特殊List子接口。

1
2
3
4
5
6
//List<string>等同于如下接口
public interface ListString extends List
//原来的E形参全部变成String类型实参
void add(String x);
Iteratoer<String> iterator();
}

通过这种方式,就解决了9.1.2节中的问题,虽然程序只定义了一个List<E>接口,但实际使用时可以产生无数多个List接口,只要为E传入不同的类型实参,系统就会多出一个新的List子接口。不过必须指出:List<String>绝不会被替换成ListString,系统没有进行源代码复制,二进制代码中没有,磁盘中没有,内存中也没有。

包含泛型声明的类型可以在定义变量、创建对象时传入一个类型实参,从而可以动态地生成无数多个逻辑上的子类,但这种子类在物理上并不存在。

任何类和接口都可以加上泛型声明

可以为任何类、接口增加泛型声明(并不是只有集合类才可以使用泛型声明,虽然集合类是泛型的重要使用场所)。

程序示例

下面自定义一个Apple类,这个Apple类就可以包含一个泛型声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 定义Apple类时使用了泛型声明
public class Apple<T> {
// 使用T类型定义实例变量
private T info;

public Apple() {
}

// 下面方法中使用T类型来定义构造器
public Apple(T info) {
this.info = info;
}

public void setInfo(T info) {
this.info = info;
}

public T getInfo() {
return this.info;
}

public static void main(String[] args) {
// 由于传给T形参的是String,所以构造器参数只能是String
Apple<String> a1 = new Apple<>("苹果");
System.out.println(a1.getInfo());
// 由于传给T形参的是Double,所以构造器参数只能是Double或double
Apple<Double> a2 = new Apple<>(5.67);
System.out.println(a2.getInfo());
}
}

上面程序定义了一个带泛型声明的Apple<T>类(不要理会这个泛型形参是否具有实际意义),使用Apple<T>类时就可为T形参传入实际类型,这样就可以生成如Apple<String>Apple<double>…形式的多个逻辑子类(物理上并不存在)。这就是9节可以使用List<String>ArrayList<String>等类型的原因—JDK在定义ListArray List等接口、类时使用了泛型声明,所以在使用这些类时为之传入了实际的类型参数。

泛型的构造器还是和类名相同

当创建带泛型声明的自定义类,为该类定义构造器时,构造器名还是原来的类名,不要增加泛型声明。例如,为Apple<T>类定义构造器,其构造器名依然是Apple,而不是Apple<T>!调用该构造器时却可以使用Apple<T>的形式,当然应该为T形参传入实际的类型参数。

Java7提供了菱形语法,允许省略<>中的类型实参。