14. Java 泛型

对于 Java 5 之前程序员而言,使用集合经常会面临一个很尴尬的问题:放入一个种特定类型,但是取出时候全部是 Object 类型,于是在具体使用时候需要将元素强转换为特定类型。但是强制类型转换是有风险的,如果不进行判断就臆断进行类型转换会发生 ClassCastException 异常。

泛型的引入可以将这些运行时异常提前到编译期暴露出来,这增强了类型安全检查。Java 5 之后提供泛型(Generics)支持,使用泛型可以最大限度地重用代码、保护类型的安全以及提高性能。泛型特性对 Java 影响最大是集合框架的使用。

自定义泛型类

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
31
32
33
34
35
36
public class Queue<T> {

// 声明保存队列元素集合 items
private List<T> items;

// 构造方法初始化是集合 items
public Queue() {
this.items = new ArrayList<T>();
}

/**
* 入队方法
* @param item 参数需要入队的元素
*/
public void queue(T item) {
this.items.add(item);
}

/**
* 出队方法
* @return 返回出队元素
*/
public T dequeue(){
if (items.isEmpty()) {
return null;
} else {
return this.items.remove(0);
}
}

@Override
public String toString() {
return items.toString();
}

}

T 是什么呢?T 表示类型参数,泛型就是类型参数化,处理的数据类型不是固定的,而是可以作为参数传入。

泛型的好处既然只使用普通类和 Object 就可以,而且泛型最后也转换为了普通类,那为什么还要用泛型呢?或者说,泛型到底有什么好处呢?泛型主要有两个好处:

  1. 更好的安全性。
  2. 更好的可读性。

语言和程序设计的一个重要目标是将 bug 尽量消灭在摇篮里,能消灭在写代码的时候,就不要等到代码写完程序运行的时候。只使用 Object,代码写错的时候,开发环境和编译器不能帮我们发现问题。

自定义泛型接口

自定义泛型接口与自定义泛型类类似,定义的方式完全一样。

泛型方法

在方法中也可以使用泛型,即方法的参数类型或返回值类型,可以用类型参数表示。实现代码如下:

1
2
3
4
// 限定类型参数为 Number
public static <T> boolean isEquals(T a, T b) {
return a.equals(b);
}

另外,泛型的类型参数也可以限定一个边界,例如比较方法 isEquals() 只想用于数值对象大小的比较,实现代码如下:

1
2
3
4
// 限定类型参数为 Number
public static <T extends Number> boolean isEquals(T a, T b) {
return a.equals(b);
}

泛型通配符

泛型方法到底应该用通配符的形式(?)还是加类型参数。两者到底有什么关系?

1)通配符形式都可以用类型参数的形式来替代,通配符能做的,用类型参数都能做。
2)通配符形式可以减少类型参数,形式上往往更为简单,可读性也更好,所以,能用通配符的就用通配符。
3)如果类型参数之间有依赖关系,或者返回值依赖类型参数,或者需要写操作,则只能用类型参数。
4)通配符形式和类型参数往往配合使用,比如,上面的 copy 方法,定义必要的类型参数,使用通配符表达依赖,并接受更广泛的数据类型。