# 泛型(参数化类型)

Java 泛型(generics)是JDK 5中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

泛型使编译时可检测到更多错误,从而提高了代码的稳定性。

  • 第一是泛化。可以用T代表任意类型Java语言中引入泛型是一个较大的功能增强,不仅语言、类型系统和编译器有了较大的变化,以支持泛型,而且类库也进行了大翻修,所以许多重要的类,比如集合框架,都已经成为泛型化的了,这带来了很多好处。
  • 第二是类型安全。泛型的一个主要目标就是提高Java程序的类型安全,使用泛型可以使编译器知道变量的类型限制,进而可以在更高程度上验证类型假设。如果不用泛型,则必须使用强制类型转换,而强制类型转换不安全,在运行期可能发生ClassCast Exception异常,如果使用泛型,则会在编译期就能发现该错误。
  • 第三是消除强制类型转换。泛型可以消除源代码中的许多强制类型转换,这样可以使代码更加可读,并减少出错的机会。
  • 第四是向后兼容。支持泛型的Java编译器(例如JDK1.5中的Javac)可以用来编译经过泛型扩充的Java程序(Generics Java程序),但是现有的没有使用泛型扩充的Java程序仍然可以用这些编译器来编译。

# 类型参数命名约定

按照约定,类型参数名称是单个大写字母。这与您已经知道的变量命名约定形成鲜明对比 ,如果没有该约定,将很难分辨类型变量与普通类或接口名称之间的区别。

最常用的类型参数名称为:

  • E - Element (used extensively by the Java Collections Framework)
  • K - Key
  • N - Number
  • T - Type
  • V - Value
  • S,U,V etc. - 2nd, 3rd, 4th types

# 泛型类

public class Box {
    private Object object;

    public void set(Object object) { this.object = object; }
    public Object get() { return object; }
}
1
2
3
4
5
6

Box泛型类如下:

/**
 * Generic version of the Box class.
 * @param <T> the type of the value being boxed
 */
public class Box<T> {
    // T stands for "Type"
    private T t;

    public void set(T t) { this.t = t; }
    public T get() { return t; }
}
1
2
3
4
5
6
7
8
9
10
11

# 泛型方法

Util类包含了一个compare的泛型方法用于比较Pair两个对象:

public class Util {
    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
        return p1.getKey().equals(p2.getKey()) &&
               p1.getValue().equals(p2.getValue());
    }
}

public class Pair<K, V> {

    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public void setKey(K key) { this.key = key; }
    public void setValue(V value) { this.value = value; }
    public K getKey()   { return key; }
    public V getValue() { return value; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 有界泛型

可以限制泛型的类型。

public class Box<T extends Integer & Long & String> {

    private T t;          

    public void set(T t) {
        this.t = t;
    }

    public T get() {
        return t;
    }

    public <U extends Number> void inspect(U u){
        System.out.println("T: " + t.getClass().getName());
        System.out.println("U: " + u.getClass().getName());
    }

    public static void main(String[] args) {
        Box<Integer> integerBox = new Box<Integer>();
        integerBox.set(new Integer(10));
        integerBox.inspect("some text"); // error: this is still String!
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 泛型类派生出的子类

注意: 给定两个具体类型AB(例如NumberInteger),无论AB是否相关,MyClass <A>MyClass <B>没有关系。MyClass <A>MyClass <B>的公共父对象是Object泛型,继承和子类型

interface PayloadList<E,P> extends List<E> {
  void setPayload(int index, P val);
  ...
}
1
2
3
4

# 通配符(Wildcards)

问号(?) 表示通配符,表示未知类型。

  • 上限通配符
// 传递进来的只能是Foo或Foo的子类
public static void process(List<? extends Foo> list) { /* ... */ }
1
2
  • 无限通配符
public static void printList(List<?> list) {
    for (Object elem: list)
        System.out.print(elem + " ");
    System.out.println();
}
1
2
3
4
5
  • 设定通配符下限
// 传递进来的只能是Integer或Integer的父类
public static void addNumbers(List<? super Integer> list) {
    for (int i = 1; i <= 10; i++) {
        list.add(i);
    }
}
1
2
3
4
5
6

# 通配符使用准则

  • In变量In变量将数据提供给代码。想象一个具有两个参数的copy方法:copy(src,dest)。该src参数提供将要复制的数据,所以它是In变量
  • Out变量Out变量保存供其他地方使用的数据。在复制示例copy(src,dest)中,dest参数接受数据,因此它是Out变量

通配符准则:

  • 使用extends关键字,使用上限通配符定义In变量
  • 使用super关键字使用下界通配符定义Out变量
  • 如果可以使用Object类中定义的方法访问In变量,请使用无界通配符。
  • 如果代码需要同时使用In变量Out变量来访问变量,则不要使用通配符。

# 类型擦除

泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。 但编译器编译完带有泛形的java程序后,生成的class文件中将不再带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为擦除

例如:

public class Node<T> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) {
        this.data = data;
        this.next = next;
    }

    public T getData() { return data; }
    // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13

由于类型参数T是无界的,因此Java编译器将其替换为Object

public class Node {

    private Object data;
    private Node next;

    public Node(Object data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Object getData() { return data; }
    // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13

参考文档

Last Updated: 3 years ago