具有嵌套通配符的编译失败调用方法

时间:2015-11-07 22:30:58

标签: java generics

我遇到了java中的泛型问题,特别是使用通配符。我有一个MapouterMap),其值是另一个MapinnerMap)元素,其值为List个元素。

请参阅以下java类,我已创建它以演示此行为。不包括导入语句。忽略方法中使用的流,这是无关紧要的:

public class SSCCE {
  public static void main(String[] args) {
    // Setup
    List<Double> doubles = Arrays.asList(1.0,2.0,3.0);
    Map<Integer, List<Double>> innerMap = new HashMap<>();
    innerMap.put(1, doubles);
    Map<String, Map<Integer, List<Double>>> outerMap = new HashMap<>();
    outerMap.put("hello", innerMap);

    // Test    
    // This method call works fine. 
    Stream<Integer> streamFromInner = getStreamOfMappingToN(innerMap);

    // This method call does not work - causes below compilation error.
    Stream<Integer> stream = getStreamOfMergedDistinctMappingToN(outerMap);
  }

  private static <T, U> Stream<T> getStreamOfMappingToN(Map<T, ? extends Collection<U>> map) {
    return map.entrySet().stream().map(q -> q.getKey());
  }

  private static <T, U> Stream<T> getStreamOfMergedDistinctMappingToN(Map<String, Map<T, ? extends Collection<U>>> map) {
    return map.entrySet().stream().flatMap(
            p -> getStreamOfMappingToN(p.getValue())
    ).distinct();
  }
}

我在第二个方法调用上看到以下编译错误:

method getStreamOfMergedDistinctMappingToN in class SSCCE cannot be applied to given types;
  required: Map<String,Map<T,? extends Collection<U>>>
  found: Map<String,Map<Integer,List<Double>>>
  reason: cannot infer type-variable(s) T,U
    (argument mismatch; Map<String,Map<Integer,List<Double>>> cannot be converted to Map<String,Map<T,? extends Collection<U>>>)
  where T,U are type-variables:
    T extends Object declared in method <T,U>getStreamOfMergedDistinctMappingToN(Map<String,Map<T,? extends Collection<U>>>)
    U extends Object declared in method <T,U>getStreamOfMergedDistinctMappingToN(Map<String,Map<T,? extends Collection<U>>>)

有人可以建议为什么会发生第二种方法调用,而不是第一种?两种方法签名都包含Map<T, ? extends Collection<U>>,但只有第二种方法签名无法与呼叫匹配。如果我用? extends Collection<U>替换有问题的List<U>,它可以正常工作,因为我实际上将它传递给List,但这似乎很懒,并不能解释为什么我会看到此错误。

我已经对此进行了一些阅读,有些人遇到了类似的问题并且已经解决了他们的问题 - 很多人都说通配符的级别很重要 - 但是如果不了解这里发生的事情就很难了让我把这个问题的其他解决方案联系起来。它仍然令人困惑。通过阅读,我发现如果我在第二个方法声明中用Map<T, ? extends Collection<U>>替换? extends Map<T, ? extends Collection<U>>,它编译得很好,但我不明白为什么,然后为什么第一个方法签名可行。

感谢您对此的反馈。

1 个答案:

答案 0 :(得分:1)

您应该将方法更改为以下内容:

private static <T, U extends Collection<?>> Stream<T> getStreamOfMappingToN(Map<T, U> map) {
    return map.entrySet().stream().map(q -> q.getKey());
}

private static <T, U extends Collection<?>> Stream<T> getStreamOfMergedDistinctMappingToN(Map<String, Map<T, U>> map) {
    return map.entrySet().stream()
            .flatMap(p -> getStreamOfMappingToN(p.getValue())).distinct();
}

更改的是,该方法不是将输入类型声明为? extends Collection<U>,而是接受Map<T, U>,但U被约束为U extends Collection<?>

此更改代表您真正想要做的事情:您输入Map。此地图有两种类型TUT可以是我们提供的任何类型,但U确实需要Collection某事,即U必须extends Collection<?>

要找出您的示例无法编译的原因,请参阅this answer(或this one):通配符仅适用于第1级,而不是更深层。正如您所发现的那样,您需要添加? extends Map<...>以抵消通配符未以递归方式应用的事实。

不过,我建议您使用第一个解决方案,因为我觉得它提供了更清晰的代码和正确的意图,而不是乱搞通配符。