Type Inference
“类型推断”是 Java 编译器查看每个方法调用和相应声明以确定使调用适用的类型参数的能力。推理算法确定参数的类型,以及确定结果是否被分配或返回的类型(如果可用)。最后,推理算法try找到适用于所有参数的“最具体”类型。
为了说明最后一点,在下面的示例中,推论确定传递给pick
方法的第二个参数的类型为Serializable
:
static <T> T pick(T a1, T a2) { return a2; }
Serializable s = pick("d", new ArrayList<String>());
类型推断和泛型方法
Generic Methods向您介绍了类型推断,它使您可以像调用普通方法一样调用泛型方法,而无需在尖括号之间指定类型。考虑以下示例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 见证*指定 type 参数,如下所示:
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
。
Note:
重要的是要注意,推理算法仅使用调用参数,目标类型,并且可能使用明显的预期返回类型。推理算法不使用程序后面的结果。
Target Types
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 中,必须按如下所示指定 type 参数值的值:
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。