Merging multiple maps in Java 8

13,208

Solution 1

One way to do it would be to create a stream of sets of entries and then flatmap it to have a stream of entries and collect those into a map.

Map<String,Animal> animals = 
    Stream.of(dogs.entrySet(), cats.entrySet(), elephants.entrySet())
          .flatMap(Set::stream)
          .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));

//Stream.of(dogs, cats, elephants).flatMap(m -> m.entrySet().stream()) could also be an option

Also not a one-liner and without streams using Map#putAll:

Map<String,Animal> animals = new HashMap<>(dogs);
animals.putAll(cats);
animals.putAll(elephants);

Solution 2

You can accomplish the task at hand with the use of Stream.Concat:

Map<String,Animal> animals = Stream.concat(Stream.concat(dogs.entrySet().stream(), cats.entrySet().stream()), elephants.entrySet().stream())
                                   .collect(Collectors.toMap
                                    (
                                       Map.Entry::getKey,
                                       Map.Entry::getValue
                                    )
                              );

You'll need to be cautious here because when merging and there are duplicate keys then an exception will be raised as expected.

Solution 3

The best way IMO is as in Alexis C.'s answer, with Map.putAll:

Map<String, Animal> animals = new HashMap<>(dogs);
animals.putAll(cats);
animals.putAll(elephants);

A variant on this one:

Map<String, Animal> animals = new HashMap<>(dogs);
cats.forEach(animals::put);
elephants.forEach(animals::put);
Share:
13,208

Related videos on Youtube

smeeb
Author by

smeeb

Updated on June 04, 2022

Comments

  • smeeb
    smeeb almost 2 years

    Java 8 here. I have the following classes:

    public interface Animal {
      ...
    }
    
    public class Dog implements Animal {
      ...
    }
    
    public class Cat implements Animal {
      ...
    }
    
    public class Elephant implements Animal {
      ...
    }
    

    I have to implement the following method:

    void doSomething(Map<String,Dog> dogs, Map<String,Cat> cats, Map<String,Elephant> elephants) {
      // TODO:
      // * Merge all dogs, cats & elephants together into the same Map<String,Animal>,
      //     but...
      // * Do so generically (without having to create, say, a HashMap instance, etc.)
    }
    

    In my doSomething(...) method, I need to merge all the map arguments into the same Map<String,Animal> map, but I'd really prefer to do so without my code having to instantiate a specific map implementation (such as HashMap, etc.).

    Meaning, I know I could do this:

    void doSomething(Map<String,Dog> dogs, Map<String,Cat> cats, Map<String,Elephant> elephants) {
      Map<String,Animal> animals = new HashMap<>();
      for(String dog : dogs.keySet()) {
        animals.put(dog, dogs.get(dog));
      }
      for(String cat : cats.keySet()) {
        animals.put(cat, cats.get(cat));
      }
      for(String elephant : elephants.keySet()) {
        animals.put(elephant, elephants.get(elephant));
      }
    
      // Now animals has all the argument maps merged into it, but is specifically
      // a HashMap...
    }
    

    I'm even fine using some utility if it exists, like maybe a Collections.merge(dogs, cats, elephants), etc. Any ideas?

    • shmosel
      shmosel over 6 years
      Why are you trying to avoid instantiating a new map?
    • smeeb
      smeeb over 6 years
      Thanks @shmosel but not looking for an XY Answer here.
    • shmosel
      shmosel over 6 years
      I'm actually trying to understand the requirement. Is it not to create a new object? Not to use a particular implementation? To use the same implementation as the arguments? To support various implementations? It's not very clear what your goal is. Note that all the answers provided so far do, in fact, instantiate a new map.
  • smeeb
    smeeb over 6 years
    Thanks @Aomine (+1) - but your code yields the following error: "Non-static method cannot be referenced from a static context"...any ideas?
  • Ousmane D.
    Ousmane D. over 6 years
    that's most likely because you're calling the method doSomething inside a static context. that's nothing to do with the solution at hand. 2 ways to solve it. 1 is to instantiate an object of the containing type and then call the doSomething from the new instance or make the doSomething method static.
  • smeeb
    smeeb over 6 years
    Ok, however doSomething(...) is not a static method. I'll start playing around with it a little.
  • smeeb
    smeeb over 6 years
    Yeah I'm following you, still something else is going on. This is a compiler (not runtime) error, and this is a library I'm building (so I don't have any other place in the code where doSomething(...) is explicitly called).
  • smeeb
    smeeb over 6 years
    Thanks @Federico but that still requires me to instantiate animals as a HashMap. It sounds like their are other (stream-based) ways to avoid this so I'm more interested in those.
  • smeeb
    smeeb over 6 years
    Thanks @Alexis C (+1) but where is your toMap(...) static method coming from?
  • shmosel
    shmosel over 6 years
    @smeeb java.util.stream.Collectors
  • shmosel
    shmosel over 6 years
    I would make a small improvement: Stream.of(dogs, cats, elephants).map(Map::entrySet)
  • Alexis C.
    Alexis C. over 6 years
    @shmosel But then you'll need to flatmap the stream i.e Stream.of(dogs, cats, elephants).map(Map::entrySet).flatMap(Set::stream). In this case, I prefer to write Stream.of(dogs, cats, elephants).flatMap(m -> m.entrySet().stream()) directly, but matter of taste I guess.
  • shmosel
    shmosel over 6 years
    @smeeb It's undocumented, but toMap() also produces a HashMap.
  • Alexis C.
    Alexis C. over 6 years
    @smeeb From the Collectors class. If you want to have a specific map implementation, you can use this overloaded method.