#类型推理
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
但是在java8中,目标类型被扩展为包括函数参数,所以写成下面这样也可以
1
| processStringList(Collections.emptyList());
|
下一页