Java教程是针对JDK 8编写的。本页面中描述的示例和实践未充分利用后续版本中引入的改进,并且可能使用不再可用的技术。
有关Java SE 9及其后续版本中更新的语言特性的摘要,请参阅Java语言更改。
有关所有JDK版本的新功能、增强功能以及已删除或弃用选项的信息,请参阅JDK发行说明。
一个泛型类型是一个参数化类型的泛型类或接口。下面的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,导致运行时错误。
使用以下格式定义泛型类:
class name<T1, T2, ..., Tn> { /* ... */ }
类型参数部分由尖括号(<>)括起来,紧跟在类名后面。它指定了类型参数(也称为类型变量)T1,T2,...,和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 类本身。
与任何其他变量声明一样,此代码实际上并不会创建一个新的 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构造函数的参数类型分别是String和Integer。由于自动装箱的原因,将String和int传递给该类是有效的。
如Diamond符号中所述,由于Java编译器可以从声明OrderedPair<String, Integer>中推断出K和V的类型,因此可以使用Diamond符号简化这些语句:
OrderedPair<String, Integer> p1 = new OrderedPair<>("Even", 8); OrderedPair<String, String> p2 = new OrderedPair<>("hello", "world");
要创建泛型接口,遵循与创建泛型类相同的约定。
您还可以使用参数化类型(例如List<String>)替换类型参数(即K或V)。例如,使用OrderedPair<K, V>的例子:
OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<Integer>(...));