为何继承原始类型的类会引发方法重载调用歧义?
咱们先来复现你遇到的问题场景,代码如下:
public class Main { private Main() {} static class Generic<T> {} @SuppressWarnings("rawtypes") static class ExtendsRaw extends Generic {} static void method(Generic<String> generic) {} static void method(ExtendsRaw raw) {} static void test() { method(new Generic<>()); // Ambiguous method call error method(new ExtendsRaw()); } }
你会发现调用method(new ExtendsRaw())时出现了重载歧义,但像下面这种普通子类型的重载调用却完全正常:
import java.util.List; import java.util.ArrayList; public class Main { private Main() {} static void method1(Object supertype) {} static void method1(String subtype) {} static void method2(List<String> supertype) {} static void method2(ArrayList<String> subtype) {} static void test() { // These both work fine. method1(new Object()); method1("string"); // These also both work fine. method2(List.of()); method2(new ArrayList<>()); } }
核心原因:原始类型的擦除特性打破了重载解析的子类型优先级
Java的重载解析逻辑是优先匹配最具体的方法——也就是当某个方法的参数类型是调用参数类型的直接/间接子类时,这个方法会被优先选中。但原始类型(raw type,比如这里的Generic不带泛型参数)的存在会干扰这个逻辑,原因有两个:
原始类型的子类会同时具备“擦除后的兼容性”和“自身类型的子类型关系”
你定义的ExtendsRaw继承的是原始类型Generic,而不是带通配符的Generic<?>。这会导致:- 一方面,
ExtendsRaw是Generic(原始类型)的子类,而原始类型Generic在擦除后和Generic<String>是兼容的(因为泛型擦除后所有Generic<T>都会变成Generic),所以new ExtendsRaw()可以被隐式转换为Generic<String>。 - 另一方面,
ExtendsRaw本身又是另一个重载方法method(ExtendsRaw raw)的参数类型,看起来像是更具体的匹配。
但Java编译器在这里无法判断哪个匹配“更具体”——因为原始类型的擦除导致
Generic<String>和ExtendsRaw之间的子类型关系变得模糊:编译器既不能确定ExtendsRaw是Generic<String>的子类(因为ExtendsRaw继承的是无参数的原始类型Generic,不是Generic<String>),也不能否定这种兼容性(因为擦除后两者都是Generic),所以就出现了歧义。- 一方面,
普通子类型的重载不存在这种模糊性
看你第二个例子里的情况:String是Object的直接子类,所以调用method1("string")时,编译器能明确判断method1(String subtype)是更具体的匹配,不会有歧义。ArrayList<String>是List<String>的直接实现类,同样,编译器能清晰识别出method2(ArrayList<String> subtype)是更具体的方法,所以调用不会出错。
这些普通子类型的关系是明确的,没有泛型擦除带来的模糊地带,所以重载解析能正常工作。
额外补充:如何解决这个歧义?
如果你想消除第一个例子里的歧义,只需要把ExtendsRaw的父类从原始类型改为带通配符的泛型类型即可:
static class ExtendsRaw extends Generic<?> {}
这样ExtendsRaw和Generic<String>之间的类型关系就清晰了——ExtendsRaw是Generic<?>的子类,但不是Generic<String>的子类,编译器就会优先匹配method(ExtendsRaw raw)这个更具体的方法。
备注:内容来源于stack exchange,提问作者Tomer Aberbach

