49

At one point in my code, I created a Set<Map.Entry<K, V>> from a map. Now I want to recreate the same map form, so I want to convert the HashSet<Map.Entry<K, V>> back into a HashMap<K, V>. Does Java have a native call for doing this, or do I have to loop over the set elements and build the map up manually?

Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
Joeblackdev
  • 7,217
  • 24
  • 69
  • 106
  • How did you created `Set` from `Map`, using key or value or custom logic? besides there is no `native` method for `HashSet` to `HashMap`. You need to iterate and use some logic as while putting into `HashMap` how you choose key and value. – harsh Apr 19 '13 at 16:02
  • @Paul thanks now question makes clear sense – harsh Apr 19 '13 at 16:03
  • 1
    I don't think `Map.Entry` is meant to be used this way. Does the implementation used by `HashMap` override `hashCode` and `equals` for example? – Paul Bellora Apr 19 '13 at 16:06
  • a Set of Map.Entry IS a Map (logically). The implementation of TreeSet/HashSet is to use a tree/hash map to backup the set. Meaning Set.add(X) == Map.put(X,whatever); – Christian Bongiorno Apr 19 '13 at 16:42

8 Answers8

82

Simpler Java-8 solution involving Collectors.toMap:

Map<Integer, String> mapFromSet = set.stream()
    .collect(Collectors.toMap(Entry::getKey, Entry::getValue));

An IllegalStateException will be thrown if duplicate key is encountered.

Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
23

There is no inbuilt API in java for direct conversion between HashSet and HashMap, you need to iterate through set and using Entry fill in map.

one approach:

Map<Integer, String> map = new HashMap<Integer, String>();
    //fill in map
    Set<Entry<Integer, String>> set = map.entrySet();

    Map<Integer, String> mapFromSet = new HashMap<Integer, String>();
    for(Entry<Integer, String> entry : set)
    {
        mapFromSet.put(entry.getKey(), entry.getValue());
    }

Though what is the purpose here, if you do any changes in Set that will also reflect in Map as set returned by Map.entrySet is backup by Map. See javadoc below:

Set<Entry<Integer, String>> java.util.Map.entrySet()

Returns a Set view of the mappings contained in this map. The set is backed by the map, so changes to the map are reflected in the set, and vice-versa. If the map is modified while an iteration over the set is in progress (except through the iterator's own remove operation, or through the setValue operation on a map entry returned by the iterator) the results of the iteration are undefined. The set supports element removal, which removes the corresponding mapping from the map, via the Iterator.remove, Set.remove, removeAll, retainAll and clear operations. It does not support the add or addAll operations.

harsh
  • 7,502
  • 3
  • 31
  • 32
9

Fairly short Java 8 solution. Can cope with duplicate keys.

    Map<Integer, String> map = new HashMap<>();
    //fill in map
    Set<Map.Entry<Integer, String>> set = map.entrySet();
    Map<Integer, String> mapFromSet = set.stream().collect(Collectors.toMap(Entry::getKey,
      Entry::getValue,
      (a,b)->b));

Edit: thanks to shmosel who deserves more credit than I do for this

Community
  • 1
  • 1
mikeyreilly
  • 6,523
  • 1
  • 26
  • 21
  • No. Your BiConsumer does not combine the Maps. – Joe Aug 29 '15 at 17:27
  • In the above code, the BiConsumer's accept method is never called. I would have preferred to use null but the collect method stupidly throws an NPE in that case. – mikeyreilly Aug 30 '15 at 08:07
  • 1
    @mikeyreilly, [the documentation](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#collect-java.util.function.Supplier-java.util.function.BiConsumer-java.util.function.BiConsumer-) does not explicitly say that the combiner is never called for sequential stream. Thus you just violate the contract of the method. – Tagir Valeev Aug 30 '15 at 10:21
  • @TagirValeev A fair point, but I'd rather violate the contract than write code that does nothing – mikeyreilly Aug 31 '15 at 12:48
  • Ok, you guys, I feel bad about breaking the contract. I've amended my solution. – mikeyreilly Aug 31 '15 at 13:00
  • 2
    You could just as easily use `Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> b)`. – shmosel Jan 06 '16 at 07:07
9

As of Guava 19 you can use ImmutableMap.copyOf(Iterable<Map.Entry<K,V>>)

Neil
  • 3,899
  • 1
  • 29
  • 25
5

As of 2018, there are some toMap utility in Apache libraries, but unfortunately none of them support Set directly so you need to do Set#toArray() first.

I left out Guava for Neil's answer which is arguably the best.

Apache Commons Lang's ArrayUtils.toMap

Map<Object, Object> map = ArrayUtils.toMap(entrySet.toArray());

// to recover the type...
@SuppressWarnings("unchecked")
Map<K, V> typedMap = (Map<K, V>)(Map<?, ?>)map;

Apache Commons Collections' MapUtils.putAll

Map<K, V> map = MapUtils.putAll(new HashMap<K, V>(), entrySet.toArray());

Java 9's Map.ofEntries

 // convert to array and recover the type...
 @SuppressWarnings("unchecked")
 Map<K, V> map = Map.ofEntries(entrySet.toArray(new Map.Entry[0]));

 // You need to copy again if you want a mutable one
 Map<K, V> hashmap = new HashMap<>(map);
snipsnipsnip
  • 2,268
  • 2
  • 33
  • 34
4

Java 9+ without suppressed warning

As of the Java 9 version, the collection interfaces provide a bunch of handy static methods allowing you to do a lot of things inline. There is a built-in solution using a static method Map.ofEntries(Map.Entry... entries) which creates an immutable map.

Map<Integer,String> map = Map.ofEntries(
        new SimpleEntry<>(1, "one"), 
        new SimpleEntry<>(2, "two"), 
        new SimpleEntry<>(3, "three"));

Note that also the Map.entry(K, V) method is available which makes everything less verbose.

Map<Integer,String> map2 = Map.ofEntries(
        Map.entry(1, "one"), 
        Map.entry(2, "two"), 
        Map.entry(3, "three"));

Assuming you have Set<Map.Entry<K, V>>, you need an array in order to use the method using Set#toArray(T[]). Explicit casting is required due to forbidden generic array creation, which is the only drawback of this solution.

Set<Map.Entry<Integer,String>> set = ...
Entry<Integer, String>[] entries = set.<Entry<Integer,String>>toArray(Entry[]::new);
Map<Integer,String> map = Map.ofEntries(entries);

Java 8

Java 8 brings Stream API which is pretty straightforward using Collectors.toMap(keyFn, valueFn).

Set<Map.Entry<Integer,String>> set = ...
Map<Integer,String> map = set.stream()
                             .collect(Collectors.toMap(Entry::getKey, Entry::getValue));

To avoid erroneous behavior on the presence of two Map.Entry with the same keys which is forbidden in dictionaries, use Collectors.toMap(keyFn, valueFn, mergeFn). Return either first or second depending on if you want to keep the first value found for a certain key or the latest one.

Map<Integer,String> map = set.stream().collect(Collectors.toMap(
        Entry::getKey, 
        Entry::getValue, 
        (first, second) -> first));   // or '-> second' if you need the latests

Java 7 and older

All, you can do is a procedural for-each iteration as the existing answer descirbes.

Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
1

In Java 8 with correct combiner

Map<Integer, String> map = new HashMap<>();
//fill in map
Set<Map.Entry<Integer, String>> set = map.entrySet();

Map<Integer, String> mapFromSet =set.stream().collect(HashMap::new,(t, u) -> t.put(u.getKey(), u.getValue()), 
(Map mapToReturn, Map otherMap) ->
    {
        otherMap.entrySet().forEach((Map.Entry entry) -> {
            mapToReturn.put(entry.getKey(),entry.getValue());
        });
        return mapToReturn;}););
Joe
  • 7,922
  • 18
  • 54
  • 83
0

I wanted to try this for a practice problem. My issue was that I needed to convert two "maps" into "sets" so I can use the math operations that come with sets, like Union, Intersection, and Difference. While learning about "Map.Entry" I found I could convert the "Maps" into "Sets" perform the math operation as required. However, I still needed to convert my return value back into a "Map" per problem requirements. I found that I could do this if you typecast the objects back into the original variables. My end result is a "Map" that includes only the Intersections of both Maps.

public static Map<String, Integer> intersect(Map<String, Integer> map_one, Map<String, Integer> map_two)
{
    Set<Map.Entry<String, Integer>> set_one = map_one.entrySet(); //Map one now a set
    Set<Map.Entry<String, Integer>> set_two = map_two.entrySet(); //Map two now a set
    set_one.retainAll(set_two); //a math intersection operation set_one and set_two whose contents are the intersection of the two sets
    Map<String, Integer> map_retur = new HashMap<>(); //instantiation  of a new Map object
    Iterator i = set_one.iterator(); //instantiation of an iterator
      while(i.hasNext())
      {
         Map.Entry me = (Map.Entry)i.next(); // instantiation of Map.Entry as me and convert i into that Map.Entry
         map_retur.put((String)me.getKey(), (Integer)me.getValue()); // store each key/value pair into the Map as required
      }
    return map_retur; // returned value is a map with only the intersections
}

Thanks Ref: https://www.tutorialspoint.com/java/java_mapentry_interface.html For how to do the iterations in the for each loop. However, it does not include the typecasting I have shown here.