Generic Types

generic types泛型类型指的是通过类型进行参数化的通用类或者接口。下面通过修改Box类来掩饰这一概念。

A Simple Box Class

首先我们来看通过使用对象来操作任意类型的非泛型Box类。它只需要两个函数:setbox中添加对象和getbox中获取对象。

public class Box {
    private Object object;

    public void set(Object object) { this.object = object; }
    public Object get() { return object; }
}

因为box的函数接受或者返回一个对象,所以只要不使用原始类型,你可以任意的传递你想要的内容。同时在贬义的时候没办法验证类被如何使用。在一部分代码中可能将一个Integer添加到box中并希望取出IntegerS,但是在另外的一部分代码中错误的传入了一个String,这样会导致runtime error(运行时报错)

A Generic Version of the Box Class

generic class(泛型类)定义格式如下:

class name<T1, T2, ..., Tn> { /* ... */ }

类名后面使用尖括号(<>)分隔出类型参数部分。它指定type parameters(类型参数,也叫type variables-类型变量)T1,T2, ...,Tn

对上面的Box类使用泛型,将代码从public class Box改成public class Box<T>来创建一个generic type declaration (泛型类型声明)。这里引入可以在类内部任何地方使用的类型变量T

新的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; }
}

如你所见所有出现Object的地方被替换为T。一个类型变量可是你指定的任何non-primitive type(非原始类型):任意的类类型,任意的接口类型,任意的数组类型,甚至可以是其他的类型变量。

我们可以使用同样的方式创建泛型接口。

Type Parameter Naming Conventions

按照惯例,类型参数的命名使用单个大写字母。这种方式和你已知的变量命名规约有着鲜明的对比,但是这是有原因的:没有这个惯例,那么将很难区分一个类型变量和一个普通的类或者接口名。

经常被使用的类型变量名如下:

  • 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

你可以在Java SE API和接下来的课程中找到这些命名方式。

Invoking and Instantiating a Generic Type

在你的代码中引用泛型Box类型,你必须用到generic type invocation(泛型类型调用),也就是将T替换为具体的值,比如Integer:

Box<Integer> integerBox;

你可以认为泛型类型调用和普通的函数调用类似,不同的是函数调用是传入参数,而泛型类型调用是传一个类型参数-这个例子中是传Integer到Box类。

Type Parameter and Type Argument Terminology:许多开发者会混淆术语type parameter(类型参数)和type argument(类型传参),但是它们是不同的。在编程时,提供type argument是为了创建一个参数化的类型,因此Foo<T>中的T就是type parameter,而Foo<String> f中的String就是type argument,本文遵循此定义。

与其他的变量声明一样,这段代码不会真的创建一个新的Box对象。它只是声明integerBox持有一个Box of Integer的引用,这就是读取Box<Integer>的方式。

调用泛型类型也被称为parameterized type(参数化类型)

为了实例化对象,我们通常使用new关键字,在类名和圆括号之间添加<Integer>:

Box<Integer> integerBox = new Box<Integer>();

The Diamond

Java SE 7和以后版本,只要编译器能从上下文中确定或者推断出类型参数,就可以使用一组空的类型参数<>来替换掉用泛型类的构造函数所需的类型参数。这对尖括号<>,被称为the diamond(菱形运算符)。例如,你可以用下面这条语句创建一个Box<Integer>实例:

Box<Integer> integerBox = new Box<>();

更多的菱形元算符信息请参考Type Inference

Multiple Type Parameters

上面提到过,一个泛型类可以有多个类型参数(parameters)。例如,实现Pair泛型接口的OrderedPair泛型类:

public interface Pair<K, V> {
    public K getKey();
    public V getValue();
}

public class OrderedPair<K, V> implements Pair<K, V> {

    private K key;
    private V value;

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

    public K getKey()    { return key; }
    public V getValue() { return value; }
}

下面的语句创建了两个OrderedPair类的实例:

Pair<String, Integer> p1 = new OrderedPair<String, Integer>("Even", 8);
Pair<String, String>  p2 = new OrderedPair<String, String>("hello", "world");

代码new OrderedPair<String, Integer>K实例化为String,将V实例化为Integer。所以OrderedPair构造函数的参数类型分别是是StringInteger。根据autoboxing,传入Stringint也是有效的。

根据上面提到的菱形运算符,由于Java编译器可以通过OrderedPair<String, Integer>的声明推断出KV的类型,所以上面的语句可以通过菱形运算符缩短成:

OrderedPair<String, Integer> p1 = new OrderedPair<>("Even", 8);
OrderedPair<String, String>  p2 = new OrderedPair<>("hello", "world");

和创建泛型类一样使用相同的规则创建泛型接口。

Parameterized Types

你也可以将一个类型参数 (i.e., K or V) 替换为参数化类型(i.e., List)。拿OrderedPair<K, V>举例:

OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<Integer>(...));

Last updated