类型推断

类型推断是Java编译器的能力,去看每个方法的调用和相关联的声明来决定类型的实参,好让调用可以进行。推断算法决定实参的类型,甚至是返回值的类型。最后,推断算法试着找出可以满足所有参数的那个类型。所以类型推断的作用就是在使用泛型类或方法时,有时并不需要指定类型参数的类型。

为了说明最后一点,在下面的例子中,可以推断出传给pick方法的第二个参数应该是实现了Serializable接口的。

static T pick(T a1, T a2) { return a2; }
Serializable s = pick("d", new ArrayList());

泛型方法中的类型推断

泛型方法中引入了类型推断,这样可以使得你调用泛型方法就和调用普通方法一样,不需要在尖括号中指定类型。

看下面的BoxDemo例子

public class Box {
private T t; // T stands for "Type"
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
public class BoxDemo {
public static  void addBox(U u,
java.util.List> boxes) {
Box box = new Box<>();
box.set(u);
boxes.add(box);
}
public static  void outputBoxes(java.util.List> boxes) {
int counter = 0;
for (Box box: boxes) {
U boxContents = box.get();
System.out.println("Box #" + counter + " contains [" +
boxContents.toString() + "]");
counter++;
}
}
public static void main(String[] args) {
java.util.ArrayList> listOfIntegerBoxes =
new java.util.ArrayList<>();
BoxDemo.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,通常编译器可以推断泛型方法的类型参数,因此不需要在调用时指定类型。因此可以像下面这样指定类型参数来调用方法

BoxDemo.addBox(Integer.valueOf(10), listOfIntegerBoxes);

也可以参略这个类型参数的指定,编译器会根据传入这个方法的实参的类型,来判断这里的类型

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

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

如果编译器可以根据上下文判断出来实例化类时的类型,那么就可以省略类型指定这部分,前面也已经介绍过,叫做“钻石操作符”

Map> myMap = new HashMap>();

可以将构造函数简化为

Map> myMap = new HashMap<>();

注意,这里必须有钻石操作符 <>,这个不能省,否则就变成raw type了。还会得到一个编译告警。

Map> myMap = new HashMap(); // unchecked conversion warning

泛型类和非泛型类中的泛型构造函数的类型推断

在泛型或者非泛型类中,构造函数都可以是泛型的。

class MyClass {
 MyClass(T t) {
// ...
}
}

看下面这句类初始化

new MyClass("")

类型初始化成了MyClass,这句明确指定了X是Integer。构造函数中也包含一个类型参数T,编译器推断T的类型是String,因为传入构造函数的类型是String。

在JDK7及以后,使用钻石操作符<>后,如下的代码,编译器就可以推断出X的类型是Integer,T的类型是String。

MyClass myObject = new MyClass<>("");

目标类型

Java编译器借助于目标类型这个概念来推断泛型方法调用的参数类型。表达式的目标类型是Java编译器根据表达式出现的地方期望出现的数据类型。

看下面这个方法 Collections.emptyList ,声明如下

static List emptyList();

下面的赋值语句

List listOne = Collections.emptyList();

这个语句期望一个List的实例,这个数据类型就是目标类型,因为方法的返回类型是List,编译器推断T一定是String。这在Java7和8都是有效的。当然你也可以显示的指定类型,虽然在这个上下文里这不是必要的。

List listOne = Collections.emptyList();

但是在另一些场景中确实必要的,看下面这个声明

void processStringList(List stringList) {
// process stringList
}

如果有如下调用,在JDK7是无法编译的。产生的报错为List cannot be converted to List

processStringList(Collections.emptyList());

因为Java7 目标类型是根据返回值类型来确定的,Collections.emptyList()的返回值类型是List,这与processStringList方法不兼容。

因此在JDK7中,必须这么写

processStringList(Collections.emptyList());

这个显示指定在JDK8已经不需要了,因为目标类型的概念被扩展到了方法的实参。在这个例子中,processStringList需要类型List,Collections.emptyList返回了类型List,因为目标类型是List,所以编译器推断T的类型是String。

在JDK8中,下面这句就是可以编译的了

processStringList(Collections.emptyList());

这一篇主要介绍了类型推断,这就是为什么在使用许多泛型方法时,并不用指定泛型的类型。