使用Stream API将Map转换为另一个Map

时间:2014-12-10 14:58:03

标签: java functional-programming java-8

我有一个Map<Long, List<Member>>()我希望生成一个Map<Member, Long>,它是通过在List<Member>中迭代Map.Entry<Long, List<Member>>并为每个成员汇总每个地图条目的键来计算的在该成员名单中。没有非功能性的方式很容易,但是如果不使用Java 8 Stream API编写自定义收集器,我就无法找到方法。我想我需要像Stream.collect(Collectors.toFlatMap)这样的东西,但Collectors中没有这样的方法。

我能找到的最佳方式是:

       longListOfMemberMap = new HashMap<Long, List<Member>>()
       longListOfMemberMap.put(10, asList(member1, member2));

       Map<Member, Long> collect = longListOfMemberMap.entrySet().stream()
       .collect(new Collector<Map.Entry<Long, List<Member>>, Map<Member, Long>, Map<Member, Long>>() {

        @Override
        public Supplier<Map<Member, Long>> supplier() {
            return HashMap::new;
        }

        @Override
        public BiConsumer<Map<Member, Long>, Map.Entry<Long, List<Member>>> accumulator() {
            return (memberLongMap, tokenRangeListEntry) -> tokenRangeListEntry.getValue().forEach(member -> {
                memberLongMap.compute(member, new BiFunction<Member, Long, Long>() {
                    @Override
                    public Long apply(Member member, Long aLong) {
                        return (aLong == null ? 0 : aLong) + tokenRangeListEntry.getKey();
                    }
                });
            });
        }

        @Override
        public BinaryOperator<Map<Member, Long>> combiner() {
            return (memberLongMap, memberLongMap2) -> {
                memberLongMap.forEach((member, value) -> memberLongMap2.compute(member, new BiFunction<Member, Long, Long>() {
                    @Override
                    public Long apply(Member member, Long aLong) {
                        return aLong + value;
                    }
                }));
                return memberLongMap2;
            };
        }

        @Override
        public Function<Map<Member, Long>, Map<Member, Long>> finisher() {
            return memberLongMap -> memberLongMap;

        }

        @Override
        public Set<Characteristics> characteristics() {
            return EnumSet.of(Characteristics.UNORDERED);
        }
    });

    // collect is equal to
    // 1. member1 -> 10
    // 2. member2 -> 10

示例中的代码采用Map&gt;作为参数并生成一个Map:

参数Map<Long, List<Member>>

// 1. 10 -> list(member1, member2)

收集的值Map<Member, Long>

// 1. member1 -> 10
// 2. member2 -> 10

然而,正如你所看到的那样,它比非功能性方式更难看。我尝试了Collectors.toMap并减少了Stream的方法,但我找不到使用几行代码的方法。

对于这个问题,哪种方式最简单,最实用?

2 个答案:

答案 0 :(得分:6)

longListOfMemberMap.entrySet().stream()
   .flatMap(entry -> entry.getValue().stream().map(
       member -> 
           new AbstractMap.SimpleImmutableEntry<>(member, entry.getKey())))
   .collect(Collectors.groupingBy(
       Entry::getKey,
       Collectors.summingLong(Entry::getValue)));

......虽然更简单但更有必要的替代方案可能看起来像

Map<Member, Long> result = new HashMap<>(); 
longListOfMemberMap.forEach((val, members) -> 
   members.forEach(member -> result.merge(member, val, Long::sum)));

答案 1 :(得分:0)

我只想指出,在依赖Collector.of并将匿名类转换为lambdas时,可以更简洁地写下您发布的代码:

Map<Member, Long> result = longListOfMemberMap.entrySet().stream()
    .collect(Collector.of(
        HashMap::new,
        (acc, item) -> item.getValue().forEach(member -> acc.compute(member,
            (x, val) -> Optional.ofNullable(val).orElse(0L) + item.getKey())),
        (m1, m2) -> {
          m1.forEach((member, val1) -> m2.compute(member, (x, val2) -> val1 + val2));
          return m2;
        }
     ));

这仍然很麻烦,但至少不是压倒性的。