类型推断
类型推断是 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(); // 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 编译器利用目标类型来推断泛型方法调用的类型参数,表达式的目标类型是 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 表达式中的目标类型。
上一篇:泛型、继承和子类型