Generic Types
generic types
泛型类型指的是通过类型进行参数化的通用类或者接口。下面通过修改Box
类来掩饰这一概念。
A Simple Box Class
首先我们来看通过使用对象来操作任意类型的非泛型Box
类。它只需要两个函数:set
向box
中添加对象和get
从box
中获取对象。
public class Box {
private Object object;
public void set(Object object) { this.object = object; }
public Object get() { return object; }
}
因为box
的函数接受或者返回一个对象,所以只要不使用原始类型,你可以任意的传递你想要的内容。同时在贬义的时候没办法验证类被如何使用。在一部分代码中可能将一个Integer
添加到box
中并希望取出IntegerS
,但是在另外的一部分代码中错误的传入了一个String
,这样会导致runtime error
(运行时报错)
A Generic Version of the Box Class
generic class
(泛型类)定义格式如下:
class name<T1, T2, ..., Tn> { /* ... */ }
类名后面使用尖括号(<>)分隔出类型参数部分。它指定type parameters
(类型参数,也叫type variables
-类型变量)T1,T2, ...,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 type
(非原始类型):任意的类类型,任意的接口类型,任意的数组类型,甚至可以是其他的类型变量。
我们可以使用同样的方式创建泛型接口。
Type Parameter Naming Conventions
按照惯例,类型参数的命名使用单个大写字母。这种方式和你已知的变量命名规约有着鲜明的对比,但是这是有原因的:没有这个惯例,那么将很难区分一个类型变量和一个普通的类或者接口名。
经常被使用的类型变量名如下:
E - Element (used extensively by the Java Collections Framework)
K - Key
N - Number
T - Type
V - Value
S,U,V etc. - 2nd, 3rd, 4th types
你可以在Java SE API
和接下来的课程中找到这些命名方式。
Invoking and Instantiating a Generic Type
在你的代码中引用泛型Box
类型,你必须用到generic type invocation
(泛型类型调用),也就是将T
替换为具体的值,比如Integer
:
Box<Integer> integerBox;
你可以认为泛型类型调用和普通的函数调用类似,不同的是函数调用是传入参数,而泛型类型调用是传一个类型参数-这个例子中是传Integer到Box类。
Type Parameter and Type Argument Terminology:许多开发者会混淆术语
type parameter
(类型参数)和type argument
(类型传参),但是它们是不同的。在编程时,提供type argument
是为了创建一个参数化的类型,因此Foo<T>
中的T
就是type parameter
,而Foo<String> f
中的String
就是type argument
,本文遵循此定义。
与其他的变量声明一样,这段代码不会真的创建一个新的Box
对象。它只是声明integerBox
持有一个Box of Integer
的引用,这就是读取Box<Integer>
的方式。
调用泛型类型也被称为parameterized type
(参数化类型)
为了实例化对象,我们通常使用new
关键字,在类名和圆括号之间添加<Integer>
:
Box<Integer> integerBox = new Box<Integer>();
The Diamond
在Java SE 7
和以后版本,只要编译器能从上下文中确定或者推断出类型参数,就可以使用一组空的类型参数<>
来替换掉用泛型类的构造函数所需的类型参数。这对尖括号<>
,被称为the diamond
(菱形运算符)。例如,你可以用下面这条语句创建一个Box<Integer>
实例:
Box<Integer> integerBox = new Box<>();
更多的菱形元算符信息请参考Type Inference
Multiple Type Parameters
上面提到过,一个泛型类可以有多个类型参数(parameters)。例如,实现Pair
泛型接口的OrderedPair
泛型类:
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
也是有效的。
根据上面提到的菱形运算符,由于Java编译器可以通过OrderedPair<String, Integer>
的声明推断出K
和V
的类型,所以上面的语句可以通过菱形运算符缩短成:
OrderedPair<String, Integer> p1 = new OrderedPair<>("Even", 8);
OrderedPair<String, String> p2 = new OrderedPair<>("hello", "world");
和创建泛型类一样使用相同的规则创建泛型接口。
Parameterized Types
你也可以将一个类型参数 (i.e., K or V) 替换为参数化类型(i.e., List)。拿OrderedPair<K, V>
举例:
OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<Integer>(...));
Last updated
Was this helpful?