【问题标题】:What's preventing me from returning a Map<String, List<String>> as a Map<String, Collection<String>>?是什么阻止我将 Map<String, List<String>> 作为 Map<String, Collection<String>> 返回?
【发布时间】:2018-06-05 04:08:23
【问题描述】:

以下代码无法编译:

  public static Map<String, Collection<String>> convert(Collection<Foo> foos) {
    return foos == null ? Maps.newHashMap()
                        : foos.stream().collect(
                            Collectors.groupingBy(
                                f -> f.getSomeEnum().name(),
                                Collectors.mapping(Foo::getVal, Collectors.toList())));
  }

除非我将返回类型更改为List

  public static Map<String, List<String>> convert(Collection<Foo> foos) {

通常我相信我应该可以,但也许泛型会引入一些歧义?

确切的错误:

Incompatible types. Required Map<String, Collection<String>> but 'collect' was inferred to R: no instance(s) of type variable(s) A, A, A, A, A, A, D, K, R, R, T, T, T, U exist so that List<T> conforms to Collection<String>

我认为细节不相关,但以防万一:

Foo是这样的:

public class Foo {
  private MyEnumType someEnum;
  private String val;
  public MyEnumType getSomeEnum();
  public String getVal();
}

我正在尝试将Foos 的列表转换为由someEnum 分组的Foos、vals 的映射。

【问题讨论】:

    标签: java inheritance polymorphism collectors


    【解决方案1】:

    Map&lt;String, List&lt;String&gt;&gt; 不是Map&lt;String, Collection&lt;String&gt;&gt; 的子类型,请参阅this 了解更多信息。

    您可以将返回类型声明为Map&lt;String, ? extends Collection&lt;String&gt;&gt;

    【讨论】:

      【解决方案2】:

      groupingBymapping 的方法签名对于下游收集器的结果类型没有差异。因此,您最终会得到 toList() 收集器的结果类型 List&lt;T&gt;。相比之下,toCollection 的类型参数 C extends Collection&lt;T&gt; 有差异,但即使没有,将 ArrayList::new 分配给 Supplier&lt;Collection&lt;…&gt;&gt; 也不会有任何问题:

      public static Map<String, Collection<String>> convert(Collection<Foo> foos) {
          return foos == null? new HashMap<>(): foos.stream().collect(
              Collectors.groupingBy(f -> f.getSomeEnum().name(),
                  Collectors.mapping(Foo::getVal, Collectors.toCollection(ArrayList::new))));
      }
      

      考虑到当前的实现,这与toList() 完全相同。但它不会受益于未来专门针对toList() 收集器所做的改进。另一种方法是继续使用toList(),但链接类型转换:

      public static Map<String, Collection<String>> convert(Collection<Foo> foos) {
          return foos == null? new HashMap<>(): foos.stream().collect(
              Collectors.groupingBy(f -> f.getSomeEnum().name(),
                  Collectors.collectingAndThen(
                      Collectors.mapping(Foo::getVal, Collectors.toList()),
                      c -> c)));
      }
      

      由于这是一个加宽转换,所以转换函数就像c -&gt; c一样简单。不幸的是,底层实现不知道这个函数的琐碎性,并且会遍历结果映射的值,用应用这个函数的结果替换每个值。

      这可以通过特殊的加宽收集器来解决:

      public static <T,A,R extends W,W> Collector<T,A,W> wideningResult(Collector<T,A,R> original) {
          return Collector.of(original.supplier(), original.accumulator(), original.combiner(),
              original.finisher().andThen(t -> t),
              original.characteristics().toArray(new Collector.Characteristics[0]));
      }
      

      这与Collectors.collectingAndThen(original, t -&gt; t) 基本相同,链接微不足道的加宽转换函数。但是它保留了原始收集器的特性,所以如果原始收集器具有IDENTITY_FINISH 特性,我们仍然会有它,这允许跳过整理操作,这对于groupingBy 意味着它不需要迭代在地图上应用该功能。

      将其应用于实际用例会产生

      public static Map<String, Collection<String>> convert(Collection<Foo> foos) {
          return foos == null? new HashMap<>(): foos.stream().collect(
              Collectors.groupingBy(f -> f.getSomeEnum().name(),
                  wideningResult(Collectors.mapping(Foo::getVal, Collectors.toList()))));
      }
      

      【讨论】:

      • 我什至没有注意到C extends Collection&lt;T&gt;...很好!还有这个具有额外特性的技巧,我们也使用它:)(它在你的名字的包中,所以我猜它可能也是我添加的)用于我知道传入的情况列表已排序...
      猜你喜欢
      • 2018-08-02
      • 1970-01-01
      • 1970-01-01
      • 2016-07-21
      • 1970-01-01
      • 2020-04-01
      • 2015-11-21
      • 2020-08-01
      • 2021-12-21
      相关资源
      最近更新 更多