记录今天在写一个工具类时遇到泛型问题:无法从通过函数引用推导出泛型类型
。
问题复现
工具函数:
public static <K, V, C extends Collection<? super V>> C getKeys(Map<K, ? extends V> map,
Supplier<? extends C> container,
Boolean filterNull,
K... keys) {
C coll = container.get();
for (K key : keys) {
V value = map.get(key);
if (filterNull && value == null) {
continue;
}
coll.add(value);
}
return coll;
}
测试函数:
public static void main(String[] args) {
Map<String, String> map = Map.of(
"1", "1",
"2", "2",
"3", "3"
);
System.out.println(getKeys(map, ArrayList::new, true, "1", "2"));
}
idea 编译错误提示:
不存在泛型变量 E 的实例,所以 ArrayList<E> 遵循了 String 类型
原因说明
根据 idea 的编译错误信息,我们可以发现:最终在调用打印函数时,匹配的是 public void println(String x)
函数,而不是 public void println(Object x)
。显然,ArrayList 并不是 String 类型,所以编译器抛出了编译异常。
那么为什么会这样?在看一下 getKeys 函数的泛型声明:
public static <K, V, C extends Collection<? super V>> C getKeys(Map<K, ? extends V> map,
Supplier<? extends C> container,
Boolean filterNull,
K... keys)
类型 C 是根据 Supplier 接口推到出来的,但我们实际传递的是一个函数引用:ArrayList::new。很明显,函数引用中是不存在泛型信息的,这就是问题所在。由于无法推断出泛型类型,所以编译器不知道该如何匹配 println 函数的重载签名。
解决方式
直接、间接的声明泛型类型,使得编译器可以正确推导,并进行函数签名的匹配。
// 1. 不使用函数引用
System.out.println(getKeys(map, () -> new ArrayList<>(), true, "1", "2"));
// 2. 通过设置返回值的泛型类型,传递给 C 泛型类型
ArrayList<String> values = getKeys(map, ArrayList::new, true, "1", "2");
System.out.println(values);