方法调用中的类型推断如何工作?结合泛型方法示例解析
这个问题的核心在于Java泛型的类型推断和**最小公共超类型(Least Upper Bound)**规则,咱们一步步拆解来理解:
1. 泛型方法的类型参数推断逻辑
先看你的泛型方法定义:
public static <T> T test (T a, T b) { // ...方法体 }
这里的<T>是一个类型参数,编译器在调用这个方法时,会根据传入的实参自动推断出T的具体类型——关键是,当有多个实参时,编译器会寻找所有实参类型的最小公共超类型,也就是所有实参都能向上转型兼容的最具体类型。
2. 针对你的调用场景的具体推断
在main方法里的调用:
test("", new ArrayList<Integer>());
传入的两个实参分别是String类型和ArrayList<Integer>类型,我们来梳理它们的类型关系:
String的父类是Object,同时实现了Serializable、Comparable<String>等接口;ArrayList<Integer>的父类是AbstractList,最终父类也是Object,同时实现了Serializable、List、Collection等接口。
这两个类型的最小公共超类型就是Object(虽然它们也共享Serializable等接口,但编译器会优先选择能覆盖所有参数的最宽泛类类型,或者说,这里Object是唯一能让两个参数都兼容的类类型)。
所以编译器会自动推断出T = Object,此时你的方法相当于被“改写”成了:
public static Object test (Object a, Object b) { System.out.println(a.getClass().getSimpleName()); System.out.println(b.getClass().getSimpleName()); b = a; return a; }
3. 为什么b = a;不会报错?
现在就很清楚了:因为此时a和b的编译类型都是Object,把一个Object类型的变量赋值给另一个Object变量,完全符合Java的类型规则,自然不会产生编译错误。
而运行时,因为泛型的类型擦除机制,方法里的变量实际都是Object类型,但getClass()是运行时方法,能获取到对象的实际类型,所以会打印出String和ArrayList。
4. 验证:显式指定类型参数会怎么样?
如果我们显式指定T的类型为String:
Learn.<String>test("", new ArrayList<Integer>());
这时候编译器就会报错,因为ArrayList<Integer>无法向上转型为String,这也反过来验证了默认的类型推断确实选择了Object作为T的类型。
内容的提问来源于stack exchange,提问作者user5818995

