这篇Java教程基于JDK1.8。教程中的示例和实践不会使用未来发行版中的优化建议。
类型推导
类型推导是Java编译器提供的用来查看方法调用与相应方法声明以确定参数(或多个参数)类型的一种能力。推理算法确定参数的类型,如果可用,还确定返回结果的类型。最后,推理算法尝试找到与所有参数一起工作最合适的类型。
为了说明这最后一点,在下面的例子中,推论确定传递给pick方法的第二个参数是Serializable类型:
static <T> T pick(T a1, T a2) { return a2; }
Serializable s = pick("d", new ArrayList<String>());
泛型方法的类型推导
泛型方法已经介绍了类型推导,它使你能够像调用普通方法一样调用泛型方法,而不需要在尖括号之间指定类型。考虑下面的示例,BoxDemo,它需要Box类:
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);
}
}
下面是示例的输出结果:
Box #0 contains [10]
Box #1 contains [20]
Box #2 contains [30]
泛型方法addBox 定义了一个名为 U 的类型参数。通常,Java编译器能通过泛型方法调用来推导出类型参数。因此,在大部分情况下,不需要指定其类型。比如:调用泛型方法addBox,可以通过类型见证来指定类型参数:
BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);
显然,当你忽略类型见证,Java编译器可以自动推导出类型参数为 Integer:
BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);
泛型类实例化的类型推导
只要编译器能够从上下文推断类型参数,就可以用一组类型参数(<>)来替换调用泛型类构造函数所需的类型参数。这对尖括号被非正式地称为菱形。
考虑下面的类型声明:
Map<String, List<String>> myMap = new HashMap<String, List<String>>();
你可以用一组空的类型参数(<>)替换构造函数的参数化类型:
Map<String, List<String>> myMap = new HashMap<>();
为使用类实例化的类型推导,必须使用空的类型参数(<>)。下面的例子将,编译器会生成一个未检查转换异常因为构造器 HashMap() 指向 HashMap 的原始类型,而不是 Map<String,List<String>> 类型:
Map<String, List<String>> myMap = new HashMap(); // unchecked conversion warning
泛型类和非泛型类的泛型构造函数的类型推导
注意,构造函数在泛型和非泛型类中都可以是泛型的(换句话说,声明它们自己的形式类型参数)。考虑下面的例子:
class MyClass<X> {
<T> MyClass(T t) {
// ...
}
}
考虑类 MyClass 的实例化:
new MyClass<Integer>("")
该语句创建参数化类型MyClass<Integer>的实例;该语句显式地为泛型类MyClass<X>形式类型参数X指定整数类型。注意,这个泛型类的构造函数包含一个正式类型参数T。编译器推断这个泛型类构造函数类型参数T的类型为String类型(因为这个构造函数的实际参数是一个String对象)。
Java SE 7之前版本的编译器能够推断出泛型构造函数的实际类型参数,类似于泛型方法。但是,如果使用<>, Java SE 7和更高版本中的编译器可以推断正在实例化的泛型类的实际类型参数。考虑下面的例子:
MyClass<Integer> myObject = new MyClass<>("");
在本例中,编译器推断泛型类MyClass<X>的形式类型参数X的类型为Integer。它为这个泛型类的构造函数的形式类型参数T推断为String。
注意: 需要注意的是,推理算法仅使用调用参数、目标类型,可能还使用明显的预期返回类型来推断类型。推理算法不使用程序后面的结果。
目标类型
Java编译器利用目标类型推断泛型方法调用的类型参数。表达式的目标类型是Java编译器期望的数据类型,这取决于表达式出现的位置。考虑Collections.emptyList ,声明如下:
static <T> List<T> emptyList();
考虑下面的赋值语句:
List<String> listOne = Collections.emptyList();
该语句期望List<String>;这个数据类型就是目标类型。因为方法emptyList返回类型为List<T>的值,所以编译器推断类型参数T必须是值字符串。这在Java SE 7和8中都适用。或者,你可以使用类型见证,并指定T的值如下:
List<String> listOne = Collections.<String>emptyList();
但是,在这种情况下没有必要这样做。但在其他情况下,这是必要的。考虑以下方法:
void processStringList(List<String> stringList) {
// process stringList
}
假设你想用一个空列表调用方法processStringList 。在Java SE 7中,下列语句不能编译:
processStringList(Collections.emptyList());
Java SE 7编译器生成的错误消息类似如下:
List<Object> cannot be converted to List<String>
编译器需要类型参数T的值,所以它从value对象开始。Collections.emptyList 的调用返回一个类型为List<Object>的值,该值与processStringList 方法不兼容。因此,在Java SE 7中,你必须如下所示指定类型参数的值:
processStringList(Collections.<String>emptyList());
在Java SE 8中不再需要这样做。目标类型的概念已经扩展为包含方法参数,例如processStringList方法的参数。在本例中,processStringList需要类型为List<String>的参数。Collections.emptyList() 返回的值是List<T>,所以使用List<String>的目标类型,编译器推断类型参数T的值是String。因此,在Java SE 8中,编译以下语句:
processStringList(Collections.emptyList());
下一篇:通配符