使用过滤器和流将地图<字符串,对象>转换为地图<字符串,设置<对象>>

2022-09-02 03:24:53

我想转换我的地图,看起来像这样:

{
  key="someKey1", value=Apple(id="1", color="green"),
  key="someKey2", value=Apple(id="2", color="red"),
  key="someKey3", value=Apple(id="3", color="green"),
  key="someKey4", value=Apple(id="4", color="red"),
}

到另一张地图,将相同颜色的所有苹果放入同一列表中:

{
  key="red", value=list={apple1, apple3},
  key="green", value=list={apple2, apple4},  
}

我尝试了以下方法:

Map<String, Set<Apple>> sortedApples = appleMap.entrySet()
    .stream()
    .collect(Collectors.toMap(l -> l.getColour, ???));

我是否在正确的轨道上?我应该为此任务使用过滤器吗?有没有更简单的方法?


答案 1

Collectors.groupingBy比此任务更适合(尽管两者都可以使用)。Collectors.toMap

Map<String, List<Apple>> sortedApples = 
    appleMap.values()
            .stream()
            .collect(Collectors.groupingBy(Apple::getColour));

或者,将它们分组到 s 使用中:Set

Map<String, Set<Apple>> sortedApples = 
    appleMap.values()
            .stream()
            .collect(Collectors.groupingBy(Apple::getColour,
                                           Collectors.mapping(Function.identity(),
                                                              Collectors.toSet())));

或(正如青峰所评论的那样):

Map<String, Set<Apple>> sortedApples = 
    appleMap.values()
            .stream()
            .collect(Collectors.groupingBy(Apple::getColour, Collectors.toSet()));

答案 2

如果要继续,可以得到如下结果:toMap

map.values()  // get the apples
   .stream() // Stream<Apple>
   .collect(toMap(Apple::getColour, // group by colour
             v ->  new HashSet<>(singleton(v)), // have values as set of apples
          (l, r) -> {l.addAll(r); return l;})); // merge colliding apples by colour
  • 通过地图进行流式传输,而不是因为我们不关心地图键。valuesentrySet
  • Apple::getColour是用于提取我们希望分组的“事物”的函数,在本例中为 s 颜色。keyMapperApple
  • v -> new HashSet<>(singleton(v))是用于生成的映射值的函数valueMapper
  • (l, r) -> {l.addAll(r); return l;}是合并函数,用于在 颜色上发生键冲突时将两个 组合在一起。HashSetApple
  • 最后,生成的映射是一个Map<String, Set<Apple>>

但这与分组BytoSet作为下游更好:

map.values().stream().collect(groupingBy(Apple::getColour, toSet()));
  • 通过地图进行流式传输,而不是因为我们不关心地图键。valuesentrySet

  • 按提供的分类函数对 's 进行分组,即 然后在 Set 中收集值,从而收集下游收集器。AppleApple::getColourtoSet

  • 最后,生成的映射是一个Map<String, Set<Apple>>

简短,可读和惯用的方法。

您也可以在没有流的情况下执行此操作:

Map<String, Set<Apple>> res = new HashMap<>();
map.values().forEach(a -> res.computeIfAbsent(a.getColour(), e -> new HashSet<>()).add(a));
  • 迭代地图,而不是因为我们不关心地图键。valuesentrySet
  • 如果指定的键尚未与值关联,则 尝试使用给定的映射函数计算其值并将其输入到映射中。然后,我们将 添加到结果集中。a.getColour()e -> new HashSet<>()Apple
  • 如果指定的键已经与某个值相关联,则返回与其关联的现有值,然后我们调用 进入集合。a.getColour()computeIfAbsentadd(a)HashSetApple
  • 最后,生成的映射是一个Map<String, Set<Apple>>

推荐