Generic Types
泛型是在类型上参数化的通用类或interface。下面的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> { /* ... */ }
类参数部分后面用尖括号(\<\>
)分隔类型参数部分。它指定类型参数(也称为类型变量)T1
,T2
,...和Tn
。
要更新Box
类以使用泛型,请通过将代码“ public class Box
”更改为“ public class Box\<T\>
”来创建泛型声明。这引入了类型变量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
代替。类型变量可以是您指定的任何“非原始”类型:任何类类型,任何interface类型,任何数组类型,甚至另一个类型变量。
可以将相同的技术应用于创建通用interface。
类型参数命名约定
按照约定,类型参数名称是单个大写字母。这与您已经知道的变量naming约定形成鲜明对比,并且有充分的理由:如果没有此约定,将很难分辨类型变量与普通类或interface名称之间的区别。
最常用的类型参数名称为:
-
E-元素(由 Java Collections Framework 广泛使用)
-
K 键
-
N-Numbers
-
T 型
-
V-值
-
S,U,V 等-第二,第三,第四类型
您将看到在 Java SE API 以及本类其余部分中使用的这些名称。
调用并实例化泛型类型
要从您的代码中引用通用Box
类,您必须执行泛型调用,该类型将T
替换为某些具体值,例如Integer
:
Box<Integer> integerBox;
您可以认为泛型类型调用与普通方法调用类似,但是您没有将参数传递给方法,而是将* type 参数* —在这种情况下为Integer
传递给Box
类本身。
Type Parameter and Type Argument Terminology:
许多开发人员可以互换使用术语“类型参数”和“类型参数”,但是这些术语并不相同。编码时,提供类型实参以创建参数化类型。因此,Foo\<T\>
中的T
是类型参数,而Foo\<String\> f
中的String
是类型参数。在使用这些术语时,本课将遵循此定义。
像任何其他变量声明一样,此代码实际上不会创建新的Box
对象。它只是声明integerBox
将保留对“ Integer
的Box
”的引用,这就是Box\<Integer\>
的读取方式。
泛型类型的调用通常称为参数化类型。
要实例化此类,请照常使用new
关键字,但应在类名和括号之间放置\<Integer\>
:
Box<Integer> integerBox = new Box<Integer>();
The Diamond
在 Java SE 7 和更高版本中,只要编译器可以从上下文确定或推断出类型参数,就可以用一组空的类型参数( <>)替换调用泛型类的构造函数所需的类型参数。 。这对尖括号\ <>被非正式地称为钻石。例如,您可以使用以下语句创建Box\<Integer\>
的实例:
Box<Integer> integerBox = new Box<>();
有关菱形符号和类型推断的更多信息,请参见Type Inference。
多种类型的参数
如前所述,泛型类可以具有多个类型参数。例如,通用的OrderedPair
类,它实现了通用的Pair
interface:
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
。由于autoboxing,将String
和int
传递给该类是有效的。
如The Diamond中所述,由于 Java 编译器可以从声明OrderedPair\<String, Integer\>
推断K
和V
类型,因此可以使用菱形表示法来缩短这些语句:
OrderedPair<String, Integer> p1 = new OrderedPair<>("Even", 8);
OrderedPair<String, String> p2 = new OrderedPair<>("hello", "world");
要创建通用interface,请遵循与创建通用类相同的约定。
Parameterized Types
您还可以用参数化类型(即List\<String\>
)替换类型参数(即K
或V
)。例如,使用OrderedPair\<K, V\>
示例:
OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<Integer>(...));