5

I have a map:

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

Outcome:
List<Id>
List<ReferenceId>

I wanna convert this map into two list of Integers. One list contains inner-map keys, and other contains inner-map value (i.e. List<Integer>)

Can anyone tell me how to do this in Java 8 using streams?

I tried this way but got Cast Exception, can not convert String to Integer.

map.values().stream()
    .map(m -> m.entrySet()
    .stream()
    .map(e -> e.getKey())
    .collect(Collectors.toList()))
    .flatMap(l -> l.stream())
    .collect(Collectors.toList());
0

4 Answers 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>
Sign up to request clarification or add additional context in comments.

Comments

7

There is no way, how your code

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());

can produce a ClassCastException, unless you managed to insert objects of wrong type into the source map via unchecked operation(s) before the Stream operation. Such a situation is called heap pollution and you should compile your entire code with all warnings enabled (javac: use option -Xlint:unchecked) and solve them.

But note that your code is unnecessarily complicated. The chain, .entrySet().stream().map(e -> e.getKey()) is streaming over the entries and mapping to the keys, so you can stream over the keys in the first place, i.e. .keySet().stream(). Then, you are collecting the stream into a List, just to invoke .stream() in the subequent flatMap step, so you can simply use the stream you already have instead:

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

Alternatively, you can let the collector do all the work:

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

Getting the values instead of the keys works similar, but requires another flatMap step to get the List elements:

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

which is equivalent to

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

Again, there’s the alternative of letting the collector do all the work:

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

or

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

2 Comments

Nice answer as usual. Am-I right saying that letting the collector do all the work may perform a little better since you save one streamification ?
@Jean-François Savard: this basically boils down to the question, whether the target collection may draw a benefit from using addAll rather than repeated add, e.g. the ArrayList will ensure that the capacity is sufficient for the entire collection. In theory, the toList collector could use an intermediate data structure avoiding the cost of capacity increase operations, but today, that doesn’t happen. Similarly, a flatMap could allow more concurrency in a parallel stream, but afaik not with the current implementation. So today, doing everything in the collector is likely to win.
3

If your value is like Map<String,Object>. And your Object is like 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())

it works

Comments

0

Adding for one more working scenario

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

Retrieving logic

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

Result will be

ListOfAllPacakages= [111, 222, 333, 444, 555, 666]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.