类型推理

Author Avatar
贾康 11月 11, 2016

#类型推理
java编译器可以从方法的调用和该方法的声明来决定类型参数的类型,从而使调用变的有效,这叫做类型推理。推理算法决定了参数类型或是返回类型,最终推理算法尝试寻找最明确的可以应用与所有参数的类型。
为了阐明上述内容,举例如下,推理决定了pick方法的第二个参数是Serializable。

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

###类型推理和泛型方法
在泛型方法这一节中我们介绍过类型推理,它可以使我们像调用普通方法一样调用泛型方法。举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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);
}
}

运行程序结果如下

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

泛型方法addBox定义了一个名为U的类型参数,一般来说,Java编译器可以推理出泛型方法调用的类型参数,因此大部分情况下,我们并不需要显式的指出参数类型。举例来说,如果我们需要调用addBox,我们可以显式的标明参数如下:

1
BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);

或者我们可以省略类型声明,Java编译器会推理出类型参数是一个Integer。

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

###类型推理和泛型类实例化
既然java编译器可以从上下文中推理出类型参数,我们可以将泛型类构造函数中的类型参数替换为<>,<>被称为the diamond(菱形)。
举例来说,考虑如下变量声明:

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
2
3
4
5
class MyClass<X> {
<T> MyClass(T t) {
// ...
}
}

我们实例化该类如下:

1
new MyClass<Integer>("")

以上代码创建了一个MyClass<Integer>类型的类,它显式的声明了Integer作为X,隐式的声明了String作为T.
从java7我们可以声明如下:

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

需要注意的是,推理算法只能从调用参数,目标类型,期望的返回值中推理类型,推理算法并不能使用该代码以后的代码来推理参数类型。

###目标类型
java编译器可以通过目标类型来推理出参数类型,表达式的目标类型是指java编译器根据表达式的位置来决定的,考虑Colections.emptyList方法,它的声明如下:

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

有如下赋值表达式。

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

以上的表达式期望一个List<String>实例,这就是目标类型。因为emptyList返回一个List<T>,编译器推断T一定是String,这在java7以后都是可用的。
但在某些情况下省略是不行的,考虑如下方法

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

如果我们在java7中这样调用:
processStringList(Collections.emptyList());

我们就会得到错误信息:List<Object> cannot be converted to List<String>
编译器需要赋给T一个值,所以它从Object开始尝试,因此Collections.emptyList返回一个List对象,这和processStringList不符。因此在java7中你必须这样写
1
processStringList(Collections.<String>emptyList());

但是在java8中,目标类型被扩展为包括函数参数,所以写成下面这样也可以

1
processStringList(Collections.emptyList());

下一页