使用Java 8流将Map of maps转换为列表

5

我有一张地图:

Map<String, Map<Integer, List<Integer>>>
e.g. Map<Name, Map<Id, List<ReferenceId>>>

Outcome:
List<Id>
List<ReferenceId>

我想把这个地图转换成两个整数列表。一个列表包含内部地图的键,另一个列表包含内部地图的值(即List<Integer>)。
有人能告诉我如何在Java 8中使用流完成这个任务吗?
我尝试了这种方式,但出现了类型转换异常,无法将字符串转换为整数。
map.values().stream()
    .map(m -> m.entrySet()
    .stream()
    .map(e -> e.getKey())
    .collect(Collectors.toList()))
    .flatMap(l -> l.stream())
    .collect(Collectors.toList());
4个回答

17
Map<String, Map<Integer, List<Integer>>> map = ...

List<Integer> keys = map.values()       // Collection<Map<Integer, List<Integer>>>
        .stream()                       // Stream<Map<Integer, List<Integer>>>
        .map(Map::keySet)               // Stream<Set<Integer>>
        .flatMap(Set::stream)           // Stream<Integer>
        .collect(Collectors.toList());  // List<Integer>

List<Integer> values = map.values()     // Collection<Map<Integer, List<Integer>>>
        .stream()                       // Stream<Map<Integer, List<Integer>>>
        .map(Map::values)               // Stream<Collection<List<Integer>>>
        .flatMap(Collection::stream)    // Stream<List<Integer>>
        .flatMap(List::stream)          // Stream<Integer>
        .collect(Collectors.toList());  // List<Integer>

7

无论如何,你的代码都是不能这样写的

List<Integer> list = map.values().stream()
    .map(m -> m.entrySet().stream()
            .map(e -> e.getKey())
            .collect(Collectors.toList()))
    .flatMap(l -> l.stream())
    .collect(Collectors.toList());

可以通过未经检查的操作将错误类型的对象插入源映射中,在Stream操作之前,会产生ClassCastException。这种情况被称为堆污染,您应该使用所有警告启用编译整个代码(javac:使用选项-Xlint:unchecked)并解决它们。
但请注意,您的代码过于复杂。链.entrySet().stream().map(e -> e.getKey())正在流式处理条目并映射到键,因此您可以在第一次处理键时进行流式处理,即.keySet().stream()。然后,您正在将流收集到List中,只是为了在随后的flatMap步骤中调用.stream(),因此您可以简单地使用已经有的流:
List<Integer> list = map.values().stream()
    .flatMap(m -> m.keySet().stream())
    .collect(Collectors.toList());

或者,您可以让收集器完成所有工作:

List<Integer> list = map.values().stream()
    .collect(ArrayList::new, (l,m) -> l.addAll(m.keySet()), List::addAll);

获取值而不是键的操作类似,但需要另一个flatMap步骤来获取List元素:

List<Integer> list = map.values().stream()
    .flatMap(m -> m.values().stream().flatMap(List::stream))
    .collect(Collectors.toList());

这相当于

List<Integer> list = map.values().stream()
    .flatMap(m -> m.values().stream())
    .flatMap(List::stream)
    .collect(Collectors.toList());

另外,还有一种选择是让收集器来完成所有工作:


List<Integer> list = map.values().stream()
    .collect(ArrayList::new, (l,m)->m.values().forEach(l::addAll), List::addAll);

或者

List<Integer> list = map.values().stream()
    .collect(ArrayList::new, (l,m)->m.forEach((k,v)->l.addAll(v)), List::addAll);

一如既往的好答案。我说让收集器完成所有工作可能会表现得更好,因为你可以节省一个流化操作,这样说对吗? - Jean-François Savard
2
@Jean-François Savard:这基本上归结为一个问题,即目标集合是否可以从使用addAll而不是重复的add中获益,例如,ArrayList将确保容量足够整个集合。理论上,toList收集器可以使用中间数据结构来避免容量增加操作的成本,但今天并没有发生。同样,flatMap可以在并行流中允许更多的并发性,但据我所知,当前实现无法做到这一点。因此,今天在收集器中完成所有操作很可能会获胜。 - Holger

3
如果您的值是像Map<String,Object>这样的形式。而且您的Object也是像Map<String,Object>这样的形式:
 Set<String> mapKeys = myMap.entryset()    //Set<Entry<String,Object>>
.stream()                                  //Stream<Entry<String,Object>>
.map(m->(Map<String,Object>) m.getValue()) //Stream<Map<String,Object>>
.map(Map::keySet)                          //Stream<Set<String>>
.flatMap(l->l.stream())                    //Stream<String>
.collect(Collectors.toSet())

它有效运作


0

增加一个工作场景

 Map<Integer, List<Integer>> existingPacakagesMap = // having value {123=[111, 222, 333], 987=[444, 555, 666]}

获取逻辑

 List<Integer>   ListOfAllPacakages= existingPacakagesMap.values().stream().flatMap(List::stream).collect(Collectors.toList());

结果将会是

所有包的列表= [111, 222, 333, 444, 555, 666]


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接