文档

Java™ 教程-Java Tutorials 中文版
泛型类型
Trail: Learning the Java Language
Lesson: Generics (Updated)

泛型类型

generic type (泛型类型) 是在类型上参数化的泛型类或接口。将修改以下 Box 类以演示该概念。

一个简单的 Box 类

首先检查对任何类型的对象进行操作的非泛型 Box 类。它只需要提供两个方法:set,它将一个对象添加到箱中,get,获取它:

public class Box {
    private Object object;

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

由于它的方法接受或返回 Object,所以只要它不是基本类型之一,你就可以随意传入任何内容。在编译时无法验证类的使用方式。代码的一部分可能会在箱中放置 Integer 并期望从中获取 Integer,而代码的另一部分可能会错误地传入 String,导致运行时错误。

Box 类的泛型版本

generic class (泛型类) 使用以下格式定义:

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

由尖括号(<>)分隔的类型形参部分跟在类名后面。它指定 type parameters (类型形参)(也称为 type variables (类型变量))T1T2,...和 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 (非基本) 类型:任何类类型,任何接口类型,任何数组类型,甚至是其他类型变量。

可以应用相同的技术来创建泛型接口。

类型形参命名规范

按照规范,类型形参名称是单个大写字母。这与你已经知道的变量 命名 规范形成鲜明对比,并且有充分的理由:没有这个规范,很难区分类型变量和普通类或接口之间的名称区别。

最常用的类型形参名称是:

你将在整个 Java SE API 和本课程的其余部分中看到这些名称。

调用和实例化泛型类型

要从代码中引用泛型 Box 类,必须执行 generic type invocation (泛型类型调用),它将 T 替换为某些具体值,例如 Integer

Box<Integer> integerBox;

你可以将泛型类型调用视为与普通方法调用类似,但不是将参数传递给方法,而是传递 type argument (类型实参) - 这种情况下是 Integer - 到 Box 类本身。


类型形参和类型实参术语: Type Parameter and Type Argument Terminology (类型形参和类型实参术语):许多开发人员互换地使用术语 "type parameter" 和 "type argument",但这些术语并不相同。编码时,提供类型实参(type arguments)以创建参数化类型(parameterized type)。因此,Foo<T> 中的 T 是一个类型形参(type parameter),而 Foo<String> f 中的 String 是一个类型实参(type argument)。本课程在使用这些术语时会遵循此定义。

与任何其他变量声明一样,此代码实际上不会创建新的 Box 对象。它只是声明 integerBox 将保存对 "IntegerBox" 的引用,这就是 Box<Integer> 的读法。

泛型类型的调用通常称为 parameterized type (参数化类型)

要实例化此类,请像往常一样使用 new 关键字,但在类名和括号之间放置 <Integer>

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

钻石(Diamond)操作符

在 Java SE 7 及更高版本中,只要编译器可以从上下文中确定或推断类型实参,就可以用一组空的类型实参(<>)替换调用泛型类的构造函数所需的类型实参。 这对尖括号 <> 非正式地称为 the diamond (钻石)(译注:diamond 有钻石和菱形的意思,这里是根据形状命名的,但不知道英文中具体含义,统一翻译为钻石)。例如,你可以使用以下语句创建 Box<Integer> 的实例:

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

有关钻石表示法和类型推断的更多信息,请参阅 Type Inference

多个类型形参

如前所述,泛型类可以有多个类型形参。例如,泛型 OrderedPair 类,它实现了泛型的 Pair 接口:

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 传递给该类是有效的。

The Diamond 中所述,因为 Java 编译器可以从语句 OrderedPair<String, Integer> 推断 KV 的类型,因此可以使用钻石表示法缩短这些语句:

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

要创建泛型接口,请遵循与创建泛型类相同的规范。

参数化类型

你还可以使用参数化类型(即 List<String>)替换类型形参(即 KV)。例如,使用 OrderedPair<K, V> 示例:

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

Previous page: Why Use Generics?
Next page: Raw Types