Java泛型合并集合映射出现编译错误的原因及解决方法
问题重现
你编写了一个泛型方法用于合并两个集合映射,但传入Map<Integer, ArrayList<String>>实例时出现编译错误:
public static <E, K> void mergeMaps(Map<K, Collection<E>> receivingMap, Map<K, Collection<E>> givingMap) { for (Map.Entry<K, Collection<E>> entry : givingMap.entrySet()) { Collection<E> someCollection = receivingMap.computeIfAbsent(entry.getKey(), k -> new ArrayList<E>()); someCollection.addAll(entry.getValue()); } } public static void main(String[] args) { Map<Integer, ArrayList<String>> map1 = new HashMap<>(); Map<Integer, ArrayList<String>> map2 = new HashMap<>(); mergeMaps(map1, map2); // 编译错误 }
错误信息:
Error:(9, 9) java: method mergeMaps in class CollectionUtil cannot be applied to given types;
required: java.util.Map<K,java.util.Collection>,java.util.Map<K,java.util.Collection >
found: java.util.Map<java.lang.Integer,java.util.ArrayList<java.lang.String>>,java.util.Map<java.lang.Integer,java.util.ArrayList<java.lang.String>>
reason: cannot infer type-variable(s) E,K (argument mismatch; java.util.Map<java.lang.Integer,java.util.ArrayList<java.lang.String>> cannot be converted to java.util.Map<K,java.util.Collection>)
错误原因
核心问题在于Java泛型的不变性:
- 虽然
ArrayList<E>是Collection<E>的子类,但Map<K, ArrayList<E>>并不是Map<K, Collection<E>>的子类型。 - 泛型默认是不变的,这是为了类型安全:如果允许
Map<K, ArrayList<E>>赋值给Map<K, Collection<E>>,你可以通过后者的引用往map中添加LinkedList<E>,而实际存储的map只能容纳ArrayList<E>,这会导致运行时类型错误。
另外,你的方法中receivingMap需要支持添加Collection<E>类型的元素(这里是ArrayList<E>),但传入的Map<Integer, ArrayList<String>>的value类型是ArrayList<String>,它是Collection<String>的子类,无法直接兼容Map<K, Collection<E>>的参数要求。
修复方案
方案1:引入第三个泛型参数,兼容特定集合类型
通过新增一个泛型参数C,限定它是Collection<E>的子类,这样方法可以接收任意类型的集合映射,同时保证类型安全:
public static <E, K, C extends Collection<E>> void mergeMaps(Map<K, C> receivingMap, Map<K, ? extends Collection<E>> givingMap) { for (Map.Entry<K, ? extends Collection<E>> entry : givingMap.entrySet()) { // 注意:这里的强制转换是因为你暂时硬编码创建ArrayList,后续可通过Supplier优化 C someCollection = receivingMap.computeIfAbsent(entry.getKey(), k -> (C) new ArrayList<E>()); someCollection.addAll(entry.getValue()); } }
修改后,调用mergeMaps(map1, map2)时,编译器会自动推断:
E = String,K = Integer,C = ArrayList<String>,完美匹配传入的参数类型。
方案2:使用通配符放宽参数限制(需注意修改逻辑)
如果你不想新增泛型参数,可以通过通配符调整方法签名,但需要确保修改receivingMap的操作安全:
public static <E, K> void mergeMaps(Map<K, ? super Collection<E>> receivingMap, Map<K, ? extends Collection<E>> givingMap) { for (Map.Entry<K, ? extends Collection<E>> entry : givingMap.entrySet()) { Collection<E> someCollection = receivingMap.computeIfAbsent(entry.getKey(), k -> new ArrayList<E>()); someCollection.addAll(entry.getValue()); } }
此时调用时,你需要将传入的Map<Integer, ArrayList<String>>转换为Map<Integer, Collection<String>>(因为Collection<String>是ArrayList<String>的超类型,符合? super Collection<E>的要求):
mergeMaps((Map<Integer, Collection<String>>) (Map<?, ?>) map1, (Map<Integer, Collection<String>>) (Map<?, ?>) map2);
不过这种方式会产生unchecked警告,不如方案1优雅,仅作为备选。
内容的提问来源于stack exchange,提问作者Antimon

