类型推断是Java编译器查看每一个方法调用和相应声明的能力,以肯定使调用适用的类型参数,推理算法肯定参数的类型,若是可用,还肯定分配或返回结果的类型,最后,推理算法尝试查找适用于全部参数的最具体类型。java
为了说明最后一点,在下面的示例中,推断肯定传递给pick
方法的第二个参数是Serializable
类型:git
static <T> T pick(T a1, T a2) { return a2; } Serializable s = pick("d", new ArrayList<String>());
泛型方法向你介绍了类型推断,它使你可以像普通方法同样调用泛型方法,而无需在尖括号之间指定类型,考虑如下示例BoxDemo,它须要Box类:github
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
,可使用类型见证指定类型参数,以下所示:segmentfault
BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);
或者,若是省略类型见证,Java编译器会自动推断(从方法的参数)类型参数是Integer
:函数
BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);
只要编译器能够从上下文中推断出类型参数,就能够用一组空的类型参数(<>
)替换调用泛型类的构造函数所需的类型参数,这对尖括号被非正式地称为菱形。code
例如,请考虑如下变量声明:对象
Map<String, List<String>> myMap = new HashMap<String, List<String>>();
你可使用一组空的类型参数(<>
)替换构造函数的参数化类型:继承
Map<String, List<String>> myMap = new HashMap<>();
请注意,要在泛型类实例化期间利用类型推断,必须使用菱形,在如下示例中,编译器生成未经检查的转换警告,由于HashMap()
构造函数引用HashMap
原始类型,而不是Map<String, List<String>>
类型:get
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());