Java泛型类型推导是Java 7中引入的一种新特性,指 Java 编译器根据上下文推断出泛型类型参数的类型。
类型推导的目的
类型推导的目的是简化泛型编程,减少代码中的冗余。在 Java 7 之前,需要在定义泛型类型或调用泛型方法时显式指定泛型类型。使用类型推导后,可以让编译器根据上下文推断出泛型类型的实际类型,从而简化代码。
Java 7 以前的要创建一个列表类型:
List<String> list = new ArrayList<String>();
在有了类型推到后,上面的代码可以改写为:
List<String> list = new ArrayList<>();
类型推导使得一个泛型方法的调用就像普通方法调用一样,可以省略尖括号(<>)。
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);
}
}
上述代码中,BoxDemo.addBox(Integer.valueOf(10), listOfIntegerBoxes);
调用泛型方法时显示指定了类型参数为 Integer
。紧接着的下面2行代码并没有显示指定类型参数类型,但不影响执行结果,因为在方法调用时,Java编译器使用类型推导根据参数类型可以指导泛型方法的类型参数的类型。
泛型类的推导规则
泛型类只有一个泛型类型参数,则编译器会将该参数的类型作为泛型类型的实际类型。
public class MyClass {
public void doSomething(T t) {
System.out.println(t);
}
}
public class Main {
public static void main(String[] args) {
MyClass myClass = new MyClass<>();
myClass.doSomething(10); // 编译器推导出泛型类型的实际类型是Integer
// 使用类型推导
MyClass myClass2 = new MyClass();
myClass2.doSomething("Hello, world!"); // 编译器推导出泛型类型的实际类型是String
}
}
这个示例代码中,myClass
实例创建时指定了类型参数是Integer
,编译器推导出泛型类型参数的实际类型是Integer
。myClass2
示例的创建会引发Java编译器报错,因为语句MyClass myClass2 = new MyClass();
使用的是原始类型,未指定明确的类型,因此泛型类MyClass
中类型参数 T 在编译时变为 Object 被对待,在语句myClass2.doSomething(“Hello, world!”);
进行方法调用时,传入的参数类型是String,编译时给出的信息有所不同。
在IDEA中给出的是 unchecked call 警告。
Unchecked call to 'doSomething(T)' as a member of raw type '...MyClass'
在vscode提示类型安全提示。
Type safety: The method doSomething(Object) belongs to the raw type MyClass. References to generic type MyClass<T> should be parameterized
泛型类有多个泛型类型参数,则编译器会根据参数之间的关系推断出泛型类型的实际类型。
public class MyClass<T, U> {
public void doSomething(T t, U u) {
// 编译器会根据参数之间的关系推断出泛型类型的实际类型
System.out.println(t);
System.out.println(u);
}
}
public class Main {
public static void main(String[] args) {
MyClass<Integer, String> myClass = new MyClass<>();
myClass.doSomething(10, "Hello, world!"); // 编译器推导出泛型类型的实际类型是Integer和String
// 使用类型推导
MyClass myClass2 = new MyClass();
myClass2.doSomething(10, "Hello, world!"); // 编译器推导出泛型类型的实际类型是Integer和String
}
}
类型推导有以下几个注意事项:
Java 编译器利用目标类型在泛型方法调用时推导类型参数。拿Collections.emptyList()
方法举例,它的声明
static <T> List<T> emptyList();
使用这个方法来进行赋值:
List<String> listOne = Collections.emptyList();
这面这个赋值语句中期望的类型是List
,而Collections.emptyList()
方法返回的数据类型是List
,这里Java编译器会推导类型T
为String
。这在Java 7和Java 8中都可以通过编译。或者使用显示的类型指名需要的类型。
List<String> listOne = Collections.<String>emptyList();
但不是所有情况下都能通过编译。
void processStringList(List<String> list) {
// ...
}
在Java 7中,向方法中传入Collections.emptyList()
就无法通过编译。
提示的错误信息类似:List cannot be converted to List。因为Collections.emptyList()
方法返回的数据类型List
不是List
的子类型,无法转换。因此Java 7中使用上面的方法时需要显示执行泛型类型。
这种情况,在Java 8中不再需要显示指定泛型类型。