Java 10自定义类继承Number构造方法泛型传参报错排查
这个问题的核心是Java泛型的不变性,尤其是嵌套泛型场景下的类型兼容性问题,咱们一步步拆解:
为什么直接传入foo会报错?
你的MyClass构造方法参数是:
Map<String, HashMap<String, ? extends Number>>
而你传入的foo类型是:
Map<String, HashMap<String, MySecondClass>>
虽然MySecondClass继承自Number,而且HashMap<String, MySecondClass>可以看作是HashMap<String, ? extends Number>的子类型,但外层的Map是泛型不变的——也就是说,Map<String, SubType>不能直接赋值给Map<String, SuperType>,哪怕SubType是SuperType的子类型。
举个简单的例子:你不能把Map<String, Integer>直接传给需要Map<String, Number>的方法,因为Java不允许这样的协变(防止你往Map<String, Number>里塞Double,但实际底层是Map<String, Integer>会导致类型错误)。你的场景是嵌套了一层泛型,但原理完全一样。
那为什么你说用Long或Integer的类似Map调用正常?大概率是你记错了场景——要么是当时的构造方法参数用了通配符修饰外层的HashMap(比如Map<String, ? extends HashMap<String, ? extends Number>>),要么是你直接创建了HashMap<String, ? extends Number>实例并放入Long/Integer,而非使用Map<String, HashMap<String, Long>>这样的具体类型。
怎么解决这个问题?
最优雅的方案是修改MyClass的构造方法参数,给外层的HashMap也加上通配符,让它支持协变:
public MyClass(Map<String, ? extends HashMap<String, ? extends Number>>) { // ... }
这样,Map<String, HashMap<String, MySecondClass>>就能被正确接收了——因为? extends HashMap<String, ? extends Number>允许任何HashMap<String, ? extends Number>的子类型(包括HashMap<String, MySecondClass>)作为外层Map的value类型。
如果你没办法修改MyClass的构造方法,另一种临时方案是在调用时进行类型转换(不推荐,因为会产生unchecked警告,但可以临时解决):
MyClass myObj = new MyClass((Map<String, HashMap<String, ? extends Number>>) (Map<?, ?>) foo);
不过这种方式会绕过编译器的类型检查,可能带来潜在的类型安全问题,优先推荐修改构造方法的参数类型。
内容的提问来源于stack exchange,提问作者Valerio.pescatori

