Bick
Bick

Reputation: 18531

Java 8 streams - How to extract all objects inside a map of maps to a new map?

I have a map of maps

siteId -> (AppName -> App) 

I want to iterate all of the Apps in the inner map and create a new map of

(appId -> App)

I do it without stream

Map<String, App> result = new HashMap<>();

siteIdToAppNameToAppMap.forEach((siteId, map) ->
   map.forEach((appName, app) ->
      result.put(app.token, app)
   )
);

How do I do it with stream?

Upvotes: 3

Views: 2208

Answers (2)

Ousmane D.
Ousmane D.

Reputation: 56453

A slightly different variant to @Anton Balaniuc's answer.

Map<String, App> resultSet = 
           siteIdToAppNameToAppMap.values()
                                  .stream()
                                  .map(Map::values)
                                  .flatMap(Collection::stream)
                                  .collect(Collectors.toMap(App::getToken, 
                                            Function.identity(), (left, right) -> {  
                                       throw new RuntimeException("duplicate key");
                                             },
                                                   HashMap::new));

This solution creates a stream from the siteIdToAppNameToAppMap map values, which we then perform a map operation to the map values yielding a Stream<Collection<App>> and then flatMap will collapse all the nested Stream<Collection<App>> to a Stream<App> and then finally the toMap collector will return a Collector that accumulates the elements into a Map whose keys are the return value of App::getToken and values are the return value of Function.identity().

The function (left, right) -> { throw new RuntimeException("duplicate key");} above is the merge function, used to resolve collisions between values associated with the same key. in this particular case, you don't need it but it's only there so we can use this overload of the toMap collector which then allows us to specify that we specifically want a HashMap instance.

All, the other toMap overloads don't guarantee on the type, mutability, serializability, or thread-safety of the Map returned.

Note - if you're not expecting duplicate keys then throwing an exception, as shown above, is the way to go as it indicates there's a problem as opposed to doing something else. However, if in the case of a duplicate key you want to return a value then you can simply change the merge function to (left, right) -> left or (left, right) -> right depending on what you want.

Upvotes: 3

Anton Balaniuc
Anton Balaniuc

Reputation: 11739

What about something like this?

siteIdToAppNameToAppMap.values()
   .stream()
   .flatMap(m -> m.values().stream())
   .collect(
        Collectors.toMap(App::getToken, Function.identity())
   );

We will need to use Stream#flatMap to extract App from nested map. So stream().values() will give us Stream<Map<AppName,App>> now we need to transform it into Stream<App> using flatMap:

Stream<Map<AppName,App>> -> flatMap ->  Stream<App>

and after is we can finally collect to a new Map<AppId,App>

Upvotes: 4

Related Questions