Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Merging multiple maps in Java 8

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?

like image 800
smeeb Avatar asked Nov 05 '17 00:11

smeeb


3 Answers

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);
like image 175
Alexis C. Avatar answered Oct 21 '22 04:10

Alexis C.


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.

like image 38
Ousmane D. Avatar answered Oct 21 '22 05:10

Ousmane D.


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);
like image 1
fps Avatar answered Oct 21 '22 05:10

fps