首页 > 解决方案 > 为什么我不能在参数中使用通配符类型来计算

问题描述

直奔主题(我知道应该避免使用通配符类型作为返回类型

我正在写这个答案和以下代码:

public static Map<?, Long> manualMap(Collection<?> c){
    Map<?, Long> map = new HashMap<>();
    c.forEach(e -> map.compute(e, (k, v) -> (v == null) ? 1 : v + 1));
    return map;
}

收到以下警告:

Required type: capture of ?

Provided: capture of ?

以及来自 IntelliJ 的建议

将变量 'map' 更改为 'Map<?, Object'

这更没有意义。自然,当我尝试应用该建议时它会失败。

最初,我虽然“好吧,这与它与计算签名不匹配的事实有关”,即:

default V compute(K key, ...) 

所以我尝试了

public class MyMap <T>{
    public static <T> void nothing(Collection<T> c){
         // Empty
    }
}

 public static Map<?, Long> manualMap(Collection<Collection<?>> c, Map<?, Long> map){
     c.forEach(MyMap::nothing);
     return map;
 }

我没有问题。

以下两个版本:

public static <T> Map<?, Long> manualMap(Collection<?> c){
    Map<T, Long> map = new HashMap<>();
    c.forEach(e -> map.compute((T) e, (k, v) -> (v == null) ? 1 : v + 1));
    return map;
}

public static Map<?, Long> manualMap(Collection<?> c){
    Map<Object, Long> map = new HashMap<>();
    c.forEach(e -> map.compute(e, (k, v) -> (v == null) ? 1 : v + 1));
    return map;
}

工作没有任何问题(除了(T)情况下的警告)。

所以问题是

为什么第一个版本不起作用?

标签: javagenericslambda

解决方案


第一种方法由于“捕获转换”而无法编译,这发生在每个声明中。你可以阅读我关于这个或这个的其他答案。但简单地说,您将在那里有两种不同的类型,您可以在编译时看到它们:

 javac --debug=verboseResolution=all

输出将包含:

.....
CAP#1 extends Object from capture of ?
CAP#2 extends Object from capture of ?
...

这意味着有两种类型已被捕获转换。这些类型彼此无关,就像你拥有它的方式一样。

另一方面:

public static <T> void nothing(Collection<T> c){

}

被称为通配符捕获方法(它“捕获”通配符),并在官方教程中记录了它的工作原理和方式;因此,您对此没有任何问题。


但这里的主要问题是您不能将任何东西(除了null)分配给通配符。因此,在您的compute示例中,第一个参数将被推断为 a?并且您不能为其分配任何内容。


推荐阅读