【问题标题】:Convert a Collection to Map<String,Collection<String>> using java 8 streams [closed]使用 java 8 流将集合转换为 Map<String,Collection<String>> [关闭]
【发布时间】:2016-08-15 00:52:25
【问题描述】:

我希望使用 java 流将 Collection&lt;Placement&gt; 转换为 Map&lt;String, Collection&lt;String&gt;&gt;,其中 Placement 类如下

public class Placement {

   private String id;

   /* Links two placements, may be same for atleast 2 placements. */
   private String futureLinkId;
}

地图的键是未来链接 ID,值是放置 ID。例如,如果有 2 个展示位置 p1 { id=1, futureLinkId=f1 }, p2 { id=2, futureLinkId=f1 } ,输出应该是Map { f1 - { id1, id2 } }

【问题讨论】:

  • Map { f1 - { id1, id2 } } 是什么意思? Java 中的 Map 需要有一个键类型,这里是 String 和一个值类型,你没有告诉我们。
  • 无论如何,解决方案可能涉及使用this method
  • @ajb f1 of datatype string,是 Map 的键,值是 Collection of strings 是具有相同未来链接 id 的展示位置的 id,在本例中为 f1。
  • @San82 请注意,根据您定义的类,无法访问任何一个 ID。

标签: java lambda java-stream


【解决方案1】:

这实际上很简单,尽管其他人提供了复杂的解决方案:

Map<String, List<String>> map = placements.stream()
        .collect(groupingBy(Placement::getFutureLinkId, mapping(Placement::getId, toList())));

【讨论】:

  • 我想肯定有其他答案“令人费解”的原因,尽管它们来自可能不那么愚蠢的人。原因是 Java 收集器愚蠢地无法被发现,因为它们与通常的 FP 风格的收集 API 完全不同。我第一次看到mapping 这个东西,很高兴知道。我发现它们根本不灵活。通常你会期望调用顶层的所有方法,但是在这个 API 中,collect 本身会变得过于复杂并且包含太多逻辑
【解决方案2】:

使用three-argument toMap 是完成您需要的一种方式。 (groupingBy 似乎是另一种方法,但我不太熟悉这种方法。)

对于toMap,您需要一个将Placement 映射到键的函数;将您的 Placement 映射到一个值的函数,在这种情况下,该值需要是一个 set 值;最后是一个函数,如果它们具有相同的键,它将把两个值合并在一起。类似的东西

Map<String,HashSet<String>> result =
    placements.stream()
        .collect(Collectors.toMap
             (placement -> placement.futureLinkId,
              placement -> new HashSet(Collections.singleton(placement.id)),
              (s1, s2) -> { HashSet<String> result = new HashSet<>(s1);
                            result.add(s2);
                            return result; }));

注意:我没有对此进行测试。如果流是顺序流,这可能无需复制 s1 即可工作。

【讨论】:

  • Java 收集器太糟糕了,我认为这是一个完美的例证。你的代码比我的简单,而我正在使用为此目的而设计的方法。但是,您确实创建了很多对象。用于构建可变单例的助手会做得更好。他们应该以 Scala 集合 API 为例,它更强大、更美观(但是,很多都是由于 Scala 特有的特性)
  • 使用 placements.stream() .collect(Collectors.groupingBy(placement.id,Collectors.toSet()));
【解决方案3】:

您可以简单地按futureLinkId 分组,然后将值映射到id。一种写法:

Map<String, List<String>> result = Stream.of(new Placement("id1", "f1"), new Placement("id2", "f1"))
        .collect(Collectors.groupingBy(Placement::getFutureLinkId))
        .entrySet()
        .stream()
        .collect(Collectors.toMap(
                Map.Entry::getKey,
                entry -> entry.getValue().stream().map(Placement::getId).collect(Collectors.toList())));
System.out.println(result);

输出:

{f1=[id1, id2]}

为了让它看起来更好看,我建议写两个助手:

public static <K, V, N> Map<K, N> mapValues(Map<K, V> map, Function<V, N> mapper) {
    return map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, mapper.compose(Map.Entry::getValue)));
}

public static <T, R> List<R> map(List<T> list, Function<T, R> mapper) {
    return list.stream().map(mapper).collect(toList());
}

然后

Map<String, List<String>> result = Stream.of(new Placement("id1", "f1"), new Placement("id2", "f1")).collect(
    collectingAndThen(groupingBy(Placement::getFutureLinkId),
                      res -> mapValues(res, list -> map(list, Placement::getId))));

System.out.println(result);

【讨论】:

  • 这个答案绝对是正确的,所以如果你想对它投反对票,请先添加评论:)
  • 不是反对者,但正确性并不是有人可能反对某个答案的唯一原因。
  • @shmosel 这正是需要评论才能对正确答案投反对票的原因。如果答案是完全错误的,没有人会责怪投反对票的人没有发表评论,但是当它是正确的时候......仅仅投反对票根本没有建设性
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-02
  • 1970-01-01
  • 2021-04-26
  • 2020-03-17
  • 1970-01-01
相关资源
最近更新 更多