为什么我不能在参数中使用通配符类型来计算

dre*_*ash 7 java generics lambda

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

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

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;
}
Run Code Online (Sandbox Code Playgroud)

得到以下警告:

Required type: capture of ?

Provided: capture of ?
Run Code Online (Sandbox Code Playgroud)

以及 IntelliJ 的建议

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

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

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

default V compute(K key, ...) 
Run Code Online (Sandbox Code Playgroud)

所以我试过了

public class MyMap <T>{
    public static <T> void nothing(Collection<T> c){
         // Empty
    }
}
Run Code Online (Sandbox Code Playgroud)

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

我没有问题。

以下两个版本:

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;
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

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

所以问题是

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

Eug*_*ene 6

第一种方法由于“捕获转换”而无法编译,这发生在每个声明中。您可以阅读我关于此此的其他答案。但简而言之,您将在那里有两种不同的类型,您可以通过以下方式进行编译:

 javac --debug=verboseResolution=all
Run Code Online (Sandbox Code Playgroud)

输出将包含:

.....
CAP#1 extends Object from capture of ?
CAP#2 extends Object from capture of ?
...
Run Code Online (Sandbox Code Playgroud)

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

另一方面:

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

}
Run Code Online (Sandbox Code Playgroud)

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


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


And*_*ner 6

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;
}
Run Code Online (Sandbox Code Playgroud)

在这里,您有三个通配符:一个用于参数,一个用于映射变量,一个用于返回值。(返回值一不是超级相关)。

您正在尝试将元素从集合(一种通配符类型)传递到映射方法(另一种通配符类型)中。

编译器不知道这两个“意味着”相同,因此它不接受需要“地图”通配符的“集合”通配符。

您可以通过类型变量指示它们是相同的类型:

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

这里,T 是一个你不知道的类型;但你知道在这两种情况下它都是相同的未知类型。

或者您可以以不需要两个通配符的方式声明它:

return c.stream().collect(groupingBy(a -> a, counting()));
Run Code Online (Sandbox Code Playgroud)