Java 教程是为 JDK 8 编写的。本页中描述的示例和实践未利用在后续版本中引入的改进。
Type inference (类型推断) 是 Java 编译器的一项能力,它可以查看每个方法的调用和相应声明以确定使调用适用的类型实参(或形参)。推断算法确定参数的类型,并且如果可用还确定赋值或返回结果的类型。最后,推断算法试图找到适用于所有参数的 most specific (最特定的) 类型。
为了说明最后一点,在下面的示例中,推断确定传递给 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
,可以使用 type witness (类型说明)(译注:witness 是证人、见证人、表明的意思) 指定类型形参,如下所示:
BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);
或者,如果省略类型说明,Java 编译器会自动推断(从方法的参数)类型形参 Integer
:
BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);
只要编译器可以从上下文中推断出类型实参,就可以用一组空的类型实参(<>
)替换调用泛型类的构造函数所需的类型形参。这对尖括号非正式地称为 the diamond (钻石操作符)。
例如,请考虑以下变量声明:
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(); // unchecked conversion warning
请注意,构造函数在泛型和非泛型类中都可以是泛型的(换句话说,声明它们自己的形式类型形参)。请考虑以下示例:
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 及更高版本中的编译器可以推断正在实例化的泛型类的实际类型形参。请考虑以下示例:
MyClass<Integer> myObject = new MyClass<>("");
在此示例中,编译器为泛型类 MyClass<X>
的形式类型形参 X
推断类型 Integer
。它推断出此泛型类的构造函数的形式类型形参 T
的类型 String
。
Java 编译器利用目标类型来推断泛型方法调用的类型形参。表达式的 target type (目标类型) 是 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());
有关详细信息,请参阅 Lambda Expressions 中的 Target Typing。