如何以惯用方式将Collection<Foo>映射为MultiMap<A,B>并一行返回新实例?
嘿,这个问题我太熟了!你提到的用Stream循环调用put的方式确实能解决问题,但确实有更优雅的实现,而且完全可以一行代码生成包含映射关系的全新Multimap实例,主要看你是否愿意用第三方库——首推Guava,它对Multimap的支持简直是量身定做的。
优雅实现方案(一行代码搞定)
首选:用Guava的Multimaps收集器
Guava专门提供了Multimaps.toMultimap()收集器,完美适配你的需求,一行代码就能生成全新的Multimap实例:
Multimap<A, B> resultMultimap = fooCollection.stream() .collect(Multimaps.toMultimap(Foo::getA, Foo::getB, HashMultimap::create));
这里的三个参数分别是:
Foo::getA:从每个Foo对象中提取作为键的A类型值Foo::getB:提取对应键的B类型值HashMultimap::create:指定要创建的Multimap实现(你也可以换成TreeMultimap::create或者其他Guava提供的Multimap类型,按需选择)
这种写法完全符合函数式编程风格,不需要手动初始化Multimap再填充,一步到位得到目标实例。
不用Guava?试试Java原生Collectors(稍繁琐但可行)
如果不想引入Guava依赖,用Java原生Stream API结合Collectors.groupingBy也能实现,不过默认会得到Map<A, List<B>>,如果要转成Multimap(比如Apache Commons Collections的ArrayListMultimap),可以这么写:
Multimap<A, B> resultMultimap = fooCollection.stream() .collect(Collectors.groupingBy(Foo::getA, Collectors.mapping(Foo::getB, Collectors.toList()))) .entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (oldVal, newVal) -> oldVal, ArrayListMultimap::create));
不过这个写法相比Guava的方案就啰嗦不少,除非有严格的依赖限制,否则还是更推荐Guava的实现。
对比你原来的写法
你之前想到的循环填充方式:
Multimap<A, B> multimap = HashMultimap.create(); fooCollection.forEach(foo -> multimap.put(foo.getA(), foo.getB()));
这个写法逻辑清晰、容易理解,完全没有问题,但用收集器的写法更贴合Stream的设计理念,代码也更紧凑,尤其是能直接在一行内完成实例创建和数据映射。
内容的提问来源于stack exchange,提问作者Andrew Cheong
相关产品推荐
相关产品推荐

