泛型类型(**generic type**是通过类型进行参数化而形成的泛型类或接口。下面的Box类将被修改以说明该概念。

一个简单的Box类

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

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

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

Box类的泛型版本

一个泛型类(generic class的定义格式如下:

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

类名后面是用尖括号(<>)分隔的类型形参部分。它指定类型形参(**type parameters**(也称为类型变量(**type variables**)T1,T2,…和Tn。
要更新Box类以使用泛型,可以通过将代码“ public class Box ”更改为“ public class Box ” 来创建泛型类型声明。这引入了类型变量T,该变量可以在类内部的任何地方使用。
进行此更改后,Box类变为:

  1. /**
  2. * Generic version of the Box class.
  3. * @param <T> the type of the value being boxed
  4. */
  5. public class Box<T> {
  6. // T stands for "Type"
  7. private T t;
  8. public void set(T t) { this.t = t; }
  9. public T get() { return t; }
  10. }

如您所见,所有出现的Object都由T代替。类型变量可以是您指定的任何非基本类型:任何类类型(class type),任何接口类型(interface type),任何数组类型(array type),甚至另一个类型变量(type variable)。
可以将相同的技术应用于创建泛型接口。

类型形参命名约定

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

  • E——元素(由Java Collections Framework广泛使用)
  • K——键
  • N——数字
  • T——类型
  • V——值
  • S,U,V等——第二,第三,第四类型

您将在Java SE API以及本课程其余部分看到使用的这些名称。

调用和实例化泛型类型

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

  1. Box<Integer> integerBox;

您可以认为泛型类型调用类似于普通方法调用,但是您没有将实参传递给方法,而是将类型实参(在这种情况下为Integer)传递给Box类本身。


Type Parameter(类型形式参数,类型形参)和Type Argument(类型实际参数,类型实参)术语: 许多开发人员经常互换使用术语“type parameter”和“type argument”,但是这些术语并不相同。编码时,提供类型实参(type argument)以创建参数化类型(parameterized type)。因此,在Foo,Ť是一种type parameter(类型形参);在Foo f中,String是一个type argument(类型实参)。在使用这些术语时,本课将遵循此定义。
个人注解Type Parameter(类型形参)Type Argument(类型实参)的关系,类似于形参实参的关系,只是前者针对类型而言)


像任何其他变量声明一样,此代码实际上不会创建新的Box对象。它只是声明integerBox将保存对“Box of Integer” 的引用,这就是读取Box 的方式。
泛型类型的调用通常称为参数化类型(**parameterized type**
要实例化此类,请像往常一样使用new关键字,但将放在类名和括号之间:

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

菱形

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

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

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

多种类型形参

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

  1. public interface Pair<K, V> {
  2. public K getKey();
  3. public V getValue();
  4. }
  5. public class OrderedPair<K, V> implements Pair<K, V> {
  6. private K key;
  7. private V value;
  8. public OrderedPair(K key, V value) {
  9. this.key = key;
  10. this.value = value;
  11. }
  12. public K getKey() { return key; }
  13. public V getValue() { return value; }
  14. }

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

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

代码new OrderedPair 将K实例化为String,并将V实例化为Integer。因此,OrderedPair的构造器的参数类型分别为String和Integer。由于自动装箱,将String和int传递给类是有效的。
菱形所述,由于Java编译器可以从声明OrderedPair 推断出K和V类型,因此可以使用菱形表示法来缩短这些语句:

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

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

参数化类型

您也可以用参数化类型(即List )替换类型形参(即K或V)。例如,使用OrderedPair 示例:

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