Java 教程是针对 JDK 8 编写的。本页面描述的示例和实践不利用后续版本中引入的改进,并可能使用不再可用的技术。
有关 Java SE 9 及后续版本中更新的语言特性的摘要,请参阅 Java 语言变更。
有关所有 JDK 版本的新功能、增强功能以及已删除或已弃用选项的信息,请参阅 JDK 发行说明。
类型推断是Java编译器的能力,它能够查看每个方法调用和相应的声明,以确定使调用适用的类型参数(或参数)。推断算法确定参数的类型,如果可用,确定结果的类型(被赋值或返回)。最后,推断算法尝试找到适用于所有参数的最具体类型。
为了说明这一点,考虑下面的例子,推断确定传递给pick方法的第二个参数的类型是Serializable:
static <T> T pick(T a1, T a2) { return a2; } Serializable s = pick("d", new ArrayList<String>());
泛型方法介绍了类型推断,它使您能够像调用普通方法一样调用泛型方法,而不需要在尖括号之间指定类型。考虑下面的例子BoxDemo
,它需要Box
类:
public class BoxDemo { public static <U> void addBox(U u, java.util.List<Box<U>> boxes) { Box<U> box = new Box<>(); box.set(u); boxes.add(box); } public static <U> void outputBoxes(java.util.List<Box<U>> boxes) { int counter = 0; for (Box<U> box: boxes) { U boxContents = box.get(); System.out.println("Box #" + counter + " contains [" + boxContents.toString() + "]"); counter++; } } public static void main(String[] args) { java.util.ArrayList<Box<Integer>> listOfIntegerBoxes = new java.util.ArrayList<>(); BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes); BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes); BoxDemo.addBox(Integer.valueOf(30), listOfIntegerBoxes); BoxDemo.outputBoxes(listOfIntegerBoxes); } }
以下是此示例的输出:
Box #0 contains [10] Box #1 contains [20] Box #2 contains [30]
泛型方法addBox
定义了一个名为U
的类型参数。一般来说,Java编译器可以推断泛型方法调用的类型参数。因此,在大多数情况下,您不需要指定它们。例如,要调用泛型方法addBox
,可以使用类型目标指定类型参数,如下所示:
BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);
或者,如果省略类型见证,Java编译器会自动推断(从方法的参数中)类型参数为Integer
:
BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);
您可以用一组空类型参数(<>
)替换调用泛型类构造函数所需的类型参数,只要编译器能够从上下文中推断出类型参数。这对尖括号在非正式的情况下被称为钻石。
例如,考虑以下变量声明:
Map<String, List<String>> myMap = new HashMap<String, List<String>>();
您可以用一组空类型参数(<>)替换构造函数的参数化类型:
Map<String, List<String>> myMap = new HashMap<>();
请注意,要在泛型类实例化过程中利用类型推断,您必须使用钻石。在以下示例中,编译器生成了一个未经检查的转换警告,因为HashMap()
构造函数引用了原始类型HashMap
,而不是Map<String, List<String>>
类型:
Map<String, List<String>> myMap = new HashMap(); // 未经检查的转换警告
请注意,构造函数可以在泛型和非泛型类中是泛型的(换句话说,声明自己的形式类型参数)。考虑以下示例:
class MyClass<X> { <T> MyClass(T t) { // ... } }
考虑对类MyClass
进行实例化:
new MyClass<Integer>("")
这个语句创建了一个参数化类型MyClass<Integer>
的实例;语句明确为泛型类MyClass<X>
的形式类型参数X
指定了类型Integer
。请注意,这个泛型类的构造函数包含一个形式类型参数T
。编译器对这个泛型类的构造函数的形式类型参数T
推断出类型String
(因为这个构造函数的实际参数是一个String
对象)。
从Java SE 7之前的版本开始,编译器能够推断泛型构造函数的实际类型参数,类似于泛型方法。然而,Java SE 7及更高版本的编译器可以推断正在实例化的泛型类的实际类型参数,如果你使用了 diamond(<>
)。考虑下面的例子:
MyClass<Integer> myObject = new MyClass<>("");
在这个例子中,编译器为泛型类MyClass<X>
的形式类型参数X
推断出类型Integer
。它为这个泛型类的构造函数的形式类型参数T
推断出类型String
。
Java编译器利用目标类型来推断泛型方法调用的类型参数。表达式的目标类型是根据表达式出现的位置而Java编译器所期望的数据类型。考虑下面的方法Collections.emptyList
的声明:
static <T> List<T> emptyList();
考虑下面的赋值语句:
List<String> listOne = Collections.emptyList();
这个语句期望一个List<String>
的实例;这个数据类型就是目标类型。因为方法emptyList
返回的是类型为List<T>
的值,编译器推断类型参数T
必须是值String
。这在Java SE 7和8中都可以工作。或者你可以使用类型证明并指定T
的值,如下所示:
List<String> listOne = Collections.<String>emptyList();
然而,在这种情况下并不需要这样做。在其他情况下是必需的。考虑下面的方法:
void processStringList(List<String> stringList) { // process stringList }
假设你想使用一个空列表调用方法processStringList
。在Java SE 7中,下面的语句无法编译:
processStringList(Collections.emptyList());
Java SE 7编译器生成类似以下的错误消息:
List<Object> cannot be converted to List<String>
编译器需要类型参数T
的值,所以它从Object
开始。因此,调用Collections.emptyList
返回类型为List<Object>
的值,与方法processStringList
不兼容。因此,在Java SE 7中,必须指定类型参数的值,如下所示:
processStringList(Collections.<String>emptyList());
在Java SE 8中,这不再是必需的。目标类型的概念已经扩展到包括方法参数,例如方法processStringList
的参数。在这种情况下,processStringList
需要一个List<String>
类型的参数。方法Collections.emptyList
返回一个List<T>
类型的值,因此使用List<String>
的目标类型,编译器推断出类型参数T
的值为String
。因此,在Java SE 8中,以下语句可以编译通过:
processStringList(Collections.emptyList());