类型推断
类型推断是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());
这一篇主要介绍了类型推断,这就是为什么在使用许多泛型方法时,并不用指定泛型的类型。