类型推断(Type inference是Java编译器查看每个方法调用和相应声明以确定使调用合适的类型实参(type argument)的能力。推断算法确定实参的类型,以及确定结果是否被分配或返回类型(如果有)。最后,推断算法尝试找到与所有实参一起使用的最具体的类型。
为了说明最后一点,在下面的示例中,推断确定传递给pick方法的第二个实参的类型为Serializable:

  1. static <T> T pick(T a1, T a2) { return a2; }
  2. Serializable s = pick("d", new ArrayList<String>());

类型推断和泛型方法

泛型方法为您引入了类型推断,使您可以像调用普通方法一样调用泛型方法,而无需在尖括号之间指定类型。考虑以下示例BoxDemo,该示例需要Box类:

  1. public class BoxDemo {
  2. public static <U> void addBox(U u,
  3. java.util.List<Box<U>> boxes) {
  4. Box<U> box = new Box<>();
  5. box.set(u);
  6. boxes.add(box);
  7. }
  8. public static <U> void outputBoxes(java.util.List<Box<U>> boxes) {
  9. int counter = 0;
  10. for (Box<U> box: boxes) {
  11. U boxContents = box.get();
  12. System.out.println("Box #" + counter + " contains [" +
  13. boxContents.toString() + "]");
  14. counter++;
  15. }
  16. }
  17. public static void main(String[] args) {
  18. java.util.ArrayList<Box<Integer>> listOfIntegerBoxes =
  19. new java.util.ArrayList<>();
  20. BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);
  21. BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);
  22. BoxDemo.addBox(Integer.valueOf(30), listOfIntegerBoxes);
  23. BoxDemo.outputBoxes(listOfIntegerBoxes);
  24. }
  25. }

以下是此示例的输出:

  1. Box #0 contains [10]
  2. Box #1 contains [20]
  3. Box #2 contains [30]

泛型方法addBox定义了一个名为U的类型参数。通常,Java编译器可以推断泛型方法调用的类型参数。因此,在大多数情况下,您不必指定它们。例如,要调用泛型方法addBox,可以使用类型见证(type witness指定类型参数,如下所示:

  1. BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);
  2. //此处Integer为类型见证

另外,如果省略类型见证,则Java编译器会自动(根据方法的参数)推断出type参数为Integer

  1. BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);

泛型类的类型推断和实例化

您可以用一组空的类型参数(<>)来替换调用泛型类的构造器所需的类型参数,只要编译器可以从上下文中推断出类型参数即可。这对尖括号被非正式地称为 菱形
例如,考虑以下变量声明:

  1. Map<String, List<String>> myMap = new HashMap<String, List<String>>();

您可以用空的类型参数集(<>)代替构造器的参数化类型:

  1. Map<String, List<String>> myMap = new HashMap<>();

请注意,要在泛型类实例化过程中利用类型推断的优势,必须使用菱形。在以下示例中,编译器生成未检查的转换警告,因为HashMap()构造器引用的是HashMap原始类型,而不是Map<String, List<String>>类型:

  1. Map<String, List<String>> myMap = new HashMap(); // unchecked conversion warning

泛型和非泛型类的类型推断和泛型构造器

请注意,构造器在泛型类和非泛型类中都可以是泛型的(换句话说,声明自己的形式类型参数)。考虑以下示例:

  1. class MyClass<X> {
  2. <T> MyClass(T t) {
  3. // ...
  4. }
  5. }

考虑以下MyClass类的实例化:

  1. new MyClass<Integer>("")

该语句创建参数化类型MyClass<Integer>的实例;该语句显式指定泛型类MyClass<X>的形式类型参数X的类型Integer。请注意,此泛型类的构造器包含一个形式类型参数T。编译器为该泛型类的构造器推断形式类型参数T的类型为String(因为该构造器的实际参数是一个String对象)。
Java SE 7之前的发行版中的编译器能够推断泛型构造器的实际类型参数,类似于泛型方法。但是,如果使用菱形(<>),则Java SE 7和更高版本中的编译器可以推断要实例化的泛型类的实际类型参数。考虑以下示例:

  1. MyClass<Integer> myObject = new MyClass<>("");

在此示例中,编译器为泛型类MyClass<X>的形式类型参数X推断类型Integer。它为该泛型类的构造器的形式类型参数T推断类型String


注意:值得注意的是,推断算法仅使用调用参数目标类型,并且可能使用明显的预期返回类型。推断算法不使用程序后面的结果。


目标类型

Java编译器利用目标类型来推断泛型方法调用的类型参数。表达式的目标类型(target type是Java编译器期望的数据类型,具体取决于表达式出现的位置。考虑方法Collections.emptyList,该方法声明如下:

  1. static <T> List<T> emptyList();

考虑以下赋值语句:

  1. List<String> listOne = Collections.emptyList();

该语句期望一个List<String>实例;此数据类型是目标类型。因为方法emptyList返回List<T>类型的值,所以编译器推断类型参数T必须是值String。这在Java SE 7和8中均适用。或者,您可以使用类型见证并按如下所示指定值T

  1. List<String> listOne = Collections.<String>emptyList();

但是,在这种情况下,这不是必需的。不过,在其他情况下这是必要的。请考虑以下方法:

  1. void processStringList(List<String> stringList) {
  2. // process stringList
  3. }

假设您要使用一个空列表调用方法processStringList。在Java SE 7中,以下语句不会编译:

  1. processStringList(Collections.emptyList());

Java SE 7编译器生成类似于以下内容的错误消息:

  1. List<Object> cannot be converted to List<String>

编译器要求type参数有一个值,T因此它以value开头Object。因此,调用会Collections.emptyList返回type的值List<Object>,该值与method不兼容processStringList。因此,在Java SE 7中,必须指定type参数值的值,如下所示:
编译器需要类型参数T的值,因此它以值Object开头。 因此,对Collections.emptyList的调用返回类型为List<Object>的值,该值与方法processStringList不兼容。 因此,在Java SE 7中,必须指定类型参数值的值,如下所示:

  1. processStringList(Collections.<String>emptyList());

在Java SE 8中,这不再是必需的。什么是目标类型的概念已扩展为包括方法参数,例如processStringList方法的参数。在这种情况下,processStringList需要List<String>类型的参数。方法Collections.emptyList返回的值为List<T>,因此使用目标类型List<String>,编译器会推断类型参数T的值为String。因此,在Java SE 8中,以下语句进行编译:

  1. processStringList(Collections.emptyList());

Lambda表达式获取更多关于目标类型的信息。