文档

Java™教程
隐藏目录
泛型类型
导航: 学习Java语言
课程: 泛型(已更新)

泛型类型

一个泛型类型是一个参数化类型的泛型类或接口。下面的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类的泛型版本

使用以下格式定义泛型类

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

类型参数部分由尖括号(<>)括起来,紧跟在类名后面。它指定了类型参数(也称为类型变量T1T2,...,和Tn

要将Box类更新为使用泛型,你需要通过将代码"public class Box"改为"public class Box<T>"来创建一个泛型类型声明。这引入了类型变量T,可以在类的任何地方使用。

通过这个改变,Box类变为:

/**
 * Box类的泛型版本。
 * @param <T> 被装箱的值的类型
 */
public class Box<T> {
    // T代表“Type”
    private T t;

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

你可以看到,所有的Object的出现都被T替换了。类型变量可以是你指定的任何非原始类型:任何类类型,任何接口类型,任何数组类型,甚至是另一个类型变量。

这个技术也可以应用于创建泛型接口。

类型参数命名约定

按照约定,类型参数名称是单个的大写字母。这与你已经了解的变量命名约定形成鲜明对比,并且有充分的理由:如果没有这个约定,很难区分类型变量和普通的类或接口名称。

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

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

调用和实例化泛型类型

要在代码中引用泛型 Box 类,您必须执行一个泛型类型调用,它将 T 替换为某个具体值,比如 Integer

Box<Integer> integerBox;

您可以将泛型类型调用视为类似于普通方法调用,但是您不是将参数传递给方法,而是将一个类型参数(在本例中为 Integer)传递给 Box 类本身。


类型参数和类型参数的术语:许多开发人员将"类型参数"和"类型参数"这两个术语互换使用,但这两个术语并不相同。在编码时,为了创建参数化类型,需要提供类型参数。因此,在使用这些术语时,Foo<T> 中的 T 是类型参数,而 Foo<String> f 中的 String 是类型参数。本课程在使用这些术语时遵循此定义。

与任何其他变量声明一样,此代码实际上并不会创建一个新的 Box 对象。它只是声明 integerBox 将保存对"Box of Integer"的引用,即 Box<Integer> 的含义。

对泛型类型的调用通常称为参数化类型

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

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

菱形操作符

在Java SE 7及更高版本中,您可以将调用泛型类的构造函数所需的类型参数替换为一组空的类型参数(<>),只要编译器可以从上下文中确定或推断出类型参数。这对尖括号,<>,在非正式场合下被称为菱形操作符。例如,您可以使用以下语句创建 Box<Integer> 的实例:

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

有关菱形符号和类型推断的更多信息,请参阅类型推断

多个类型参数

如前所述,泛型类可以有多个类型参数。例如,泛型 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。由于自动装箱的原因,将Stringint传递给该类是有效的。

Diamond符号中所述,由于Java编译器可以从声明OrderedPair<String, Integer>中推断出KV的类型,因此可以使用Diamond符号简化这些语句:

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>(...));

上一页:为什么使用泛型?
下一页:原始类型