How to convert List to Map?
Asked Answered
W

20

301

Recently I have conversation with a colleague about what would be the optimal way to convert List to Map in Java and if there any specific benefits of doing so.

I want to know optimal conversion approach and would really appreciate if any one can guide me.

Is this good approach:

List<Object[]> results;
Map<Integer, String> resultsMap = new HashMap<Integer, String>();
for (Object[] o : results) {
    resultsMap.put((Integer) o[0], (String) o[1]);
}
Wordsmith answered 9/11, 2010 at 20:39 Comment(6)
What's the best optimal way? Optimization is done with certain parameter (speed/memory) in mind.Flashbulb
List differs from Map in the conceptual way -- Map have notion of 'key, value' pair, whereas List doesn't. Given this it's unclear how exactly you going to convert from List to Map and back.Winonawinonah
@Daniel: By Optimal, I meant what is the best way of doing so among all different ways between am not sure of all the ways and so it would be good to see some different ways of converting list to map.Wordsmith
possible duplicate of Java: how to convert a List<?> to a Map<String,?>Concessionaire
Read this article: How to Convert List to Map in JavaDiarrhoea
Java 8 HashMap and not any map implementation : https://mcmap.net/q/99681/-how-to-convert-list-to-mapDerivation
L
220
List<Item> list;
Map<Key,Item> map = new HashMap<Key,Item>(list.size());
for (Item i : list) map.put(i.getKey(),i);

Assuming of course that each Item has a getKey() method that returns a key of the proper type.

Launcelot answered 9/11, 2010 at 20:45 Comment(10)
You could also key on the position in the list.Onassis
@Jim: Do i need to set the getKey() to any specific parameter ?Wordsmith
Also what would be the value in the Map, can you elaborate with an example ?Wordsmith
@Wordsmith -- The value is the item in the list, and the key is something that makes the item unique, which is determined by you. Jim's use of getKey() was arbitrary.Onassis
@Jeremy: Ok. So basically I have element position in the list as the key and its value as value to populate the map, right ?Wordsmith
What could be other ways of doing so ?Wordsmith
Also, I have updated question with one approach that I have used, is this correct way of doing so ?Wordsmith
@Wordsmith No, you are using the same element as the key and value for ALL the objects. This will result in only one element being added to the list. If the list contains n elements, you will be adding the same duplicate map entry n times.Launcelot
You know the size beforehand so you can do Map<Key,Item> map = new HashMap<Key,Item>(list.size());Get
Clean and adequate, and does not require a callback function for every item you process. By far the best response no matter what fancy stream or utility function is available.Khudari
S
462

With , you'll be able to do this in one line using streams, and the Collectors class.

Map<String, Item> map = 
    list.stream().collect(Collectors.toMap(Item::getKey, item -> item));

Short demo:

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class Test{
    public static void main (String [] args){
        List<Item> list = IntStream.rangeClosed(1, 4)
                                   .mapToObj(Item::new)
                                   .collect(Collectors.toList()); //[Item [i=1], Item [i=2], Item [i=3], Item [i=4]]

        Map<String, Item> map = 
            list.stream().collect(Collectors.toMap(Item::getKey, item -> item));

        map.forEach((k, v) -> System.out.println(k + " => " + v));
    }
}
class Item {

    private final int i;

    public Item(int i){
        this.i = i;
    }

    public String getKey(){
        return "Key-"+i;
    }

    @Override
    public String toString() {
        return "Item [i=" + i + "]";
    }
}

Output:

Key-1 => Item [i=1]
Key-2 => Item [i=2]
Key-3 => Item [i=3]
Key-4 => Item [i=4]

As noted in comments, you can use Function.identity() instead of item -> item, although I find i -> i rather explicit.

And to be complete note that you can use a binary operator if your function is not bijective. For example let's consider this List and the mapping function that for an int value, compute the result of it modulo 3:

List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5, 6);
Map<String, Integer> map = 
    intList.stream().collect(toMap(i -> String.valueOf(i % 3), i -> i));

When running this code, you'll get an error saying java.lang.IllegalStateException: Duplicate key 1. This is because 1 % 3 is the same as 4 % 3 and hence have the same key value given the key mapping function. In this case you can provide a merge operator.

Here's one that sum the values; (i1, i2) -> i1 + i2; that can be replaced with the method reference Integer::sum.

Map<String, Integer> map = 
    intList.stream().collect(toMap(i -> String.valueOf(i % 3), 
                                   i -> i, 
                                   Integer::sum));

which now outputs:

0 => 9 (i.e 3 + 6)
1 => 5 (i.e 1 + 4)
2 => 7 (i.e 2 + 5)
Shorthand answered 2/1, 2014 at 16:58 Comment(8)
better use Function.identity() instead of item -> itemJudsen
@EmmanuelTouzery Well, Function.identity() returns t -> t;.Shorthand
Sure, both work. I guess it's a matter of taste. I find Function.identity() more immediately recognizable.Judsen
OP doesnt handle any pojos, just strings and integer on whom you cannot cal ::getKey.Sharpeyed
@Blauhirn I know, my example is based on the custom class just below. You are free to use whatever function to generate your keys from the values .Shorthand
Using streams you will loose on performance, unless you are processing really big datasets - read comments here: #22658822 And here you can find more general an article on this: jaxenter.com/…Pasargadae
FYI, stream API was added in API Level 24 in AndroidQuinby
Nice one @AlexisC. this was very helpful to me :)Ivanaivanah
L
220
List<Item> list;
Map<Key,Item> map = new HashMap<Key,Item>(list.size());
for (Item i : list) map.put(i.getKey(),i);

Assuming of course that each Item has a getKey() method that returns a key of the proper type.

Launcelot answered 9/11, 2010 at 20:45 Comment(10)
You could also key on the position in the list.Onassis
@Jim: Do i need to set the getKey() to any specific parameter ?Wordsmith
Also what would be the value in the Map, can you elaborate with an example ?Wordsmith
@Wordsmith -- The value is the item in the list, and the key is something that makes the item unique, which is determined by you. Jim's use of getKey() was arbitrary.Onassis
@Jeremy: Ok. So basically I have element position in the list as the key and its value as value to populate the map, right ?Wordsmith
What could be other ways of doing so ?Wordsmith
Also, I have updated question with one approach that I have used, is this correct way of doing so ?Wordsmith
@Wordsmith No, you are using the same element as the key and value for ALL the objects. This will result in only one element being added to the list. If the list contains n elements, you will be adding the same duplicate map entry n times.Launcelot
You know the size beforehand so you can do Map<Key,Item> map = new HashMap<Key,Item>(list.size());Get
Clean and adequate, and does not require a callback function for every item you process. By far the best response no matter what fancy stream or utility function is available.Khudari
C
113

Just in case this question isn't closed as a duplicate, the right answer is to use Google Collections:

Map<String,Role> mappedRoles = Maps.uniqueIndex(yourList, new Function<Role,String>() {
  public String apply(Role from) {
    return from.getName(); // or something else
  }});
Concessionaire answered 15/12, 2011 at 9:32 Comment(5)
"Guava contains a strictly compatible superset of the old, deprecated Google Collections Library. You should not use that library anymore." An update might be needed.Moravian
Use of an external library for such a simple operation is overkill. That or a sign of a very weak standard library. In this case @jim-garrison's answer is perfectly reasonable. It's sad that java doesn't have helpful methods like "map" and "reduce" but not entirely necessary.Veterinary
This uses Guava. Unfortunately Guava is super slow on Android, so this solution shouldn't be used in an Android project.Girandole
if the item in the list has duplicated roleNames, the above code will throws exception,Mohican
This returns a ImmutableMap. Is there an alternative to this which returns a normal Mutable Map?Michail
D
47

Short and sweet.

Using Java 8 you can do following :

Map<Key, Value> result= results
                       .stream()
                       .collect(Collectors.toMap(Value::getName,Function.identity()));

Value can be any object you use.

Dumbhead answered 1/3, 2018 at 19:56 Comment(0)
D
21

Alexis has already posted an answer in Java 8 using method toMap(keyMapper, valueMapper). As per doc for this method implementation:

There are no guarantees on the type, mutability, serializability, or thread-safety of the Map returned.

So in case we are interested in a specific implementation of Map interface e.g. HashMap then we can use the overloaded form as:

Map<String, Item> map2 =
                itemList.stream().collect(Collectors.toMap(Item::getKey, //key for map
                        Function.identity(),    // value for map
                        (o,n) -> o,             // merge function in case of conflict with keys
                        HashMap::new));         // map factory - we want HashMap and not any Map implementation

Though using either Function.identity() or i->i is fine but it seems Function.identity() instead of i -> i might save some memory as per this related answer.

Derivation answered 10/12, 2017 at 7:22 Comment(2)
The funny fact that in 2019 huge amount of people still not realize that they are not know real Map implementation they get with lambda! Actually this is just one answer I found with Java 8 lambdas I would use on production.Colophony
Is there a way to collect by specifying a Map type but without using a merge function?Kloster
C
18

Since Java 8, the answer by @ZouZou using the Collectors.toMap collector is certainly the idiomatic way to solve this problem.

And as this is such a common task, we can make it into a static utility.

That way the solution truly becomes a one-liner.

/**
 * Returns a map where each entry is an item of {@code list} mapped by the
 * key produced by applying {@code mapper} to the item.
 *
 * @param list the list to map
 * @param mapper the function to produce the key from a list item
 * @return the resulting map
 * @throws IllegalStateException on duplicate key
 */
public static <K, T> Map<K, T> toMapBy(List<T> list,
        Function<? super T, ? extends K> mapper) {
    return list.stream().collect(Collectors.toMap(mapper, Function.identity()));
}

And here's how you would use it on a List<Student>:

Map<Long, Student> studentsById = toMapBy(students, Student::getId);
Coulter answered 27/10, 2014 at 18:40 Comment(3)
For a discussion of the type parameters of this method see my follow-up question.Coulter
@EMM Of course, as intended and documented in the Javadoc.Coulter
Yes, updated the answer for covering the case of duplicates. Please review. ThanksCrannog
D
10

A List and Map are conceptually different. A List is an ordered collection of items. The items can contain duplicates, and an item might not have any concept of a unique identifier (key). A Map has values mapped to keys. Each key can only point to one value.

Therefore, depending on your List's items, it may or may not be possible to convert it to a Map. Does your List's items have no duplicates? Does each item have a unique key? If so then it's possible to put them in a Map.

Deadhead answered 9/11, 2010 at 20:52 Comment(0)
E
9

There is also a simple way of doing this using Maps.uniqueIndex(...) from Google libraries

Expand answered 2/4, 2012 at 22:17 Comment(0)
M
6

Using java-8 streams

Map<Integer, String> map = results.stream().collect(Collectors.toMap(e -> ((Integer) e[0]), e -> (String) e[1]));
Metaplasia answered 18/1, 2020 at 12:9 Comment(0)
N
5

Universal method

public static <K, V> Map<K, V> listAsMap(Collection<V> sourceList, ListToMapConverter<K, V> converter) {
    Map<K, V> newMap = new HashMap<K, V>();
    for (V item : sourceList) {
        newMap.put( converter.getKey(item), item );
    }
    return newMap;
}

public static interface ListToMapConverter<K, V> {
    public K getKey(V item);
}
Noncommittal answered 23/11, 2011 at 23:10 Comment(1)
How to use this? What should I pass as the converter parameter in the method?Girandole
S
4

Without java-8, you'll be able to do this in one line Commons collections, and the Closure class

List<Item> list;
@SuppressWarnings("unchecked")
Map<Key, Item> map  = new HashMap<Key, Item>>(){{
    CollectionUtils.forAllDo(list, new Closure() {
        @Override
        public void execute(Object input) {
            Item item = (Item) input;
            put(i.getKey(), item);
        }
    });
}};
Stood answered 15/5, 2015 at 15:0 Comment(0)
H
4

like already said, in java-8 we have the concise solution by Collectors:

  list.stream().collect(
         groupingBy(Item::getKey)
        )

and also, you can nest multiple group passing an other groupingBy method as second parameter:

  list.stream().collect(
         groupingBy(Item::getKey, groupingBy(Item::getOtherKey))
        )

In this way, we'll have multi level map, like this: Map<key, Map<key, List<Item>>>

Harassed answered 14/1, 2019 at 16:21 Comment(0)
O
3

A Java 8 example to convert a List<?> of objects into a Map<k, v>:

List<Hosting> list = new ArrayList<>();
list.add(new Hosting(1, "liquidweb.com", new Date()));
list.add(new Hosting(2, "linode.com", new Date()));
list.add(new Hosting(3, "digitalocean.com", new Date()));

//example 1
Map<Integer, String> result1 = list.stream().collect(
    Collectors.toMap(Hosting::getId, Hosting::getName));

System.out.println("Result 1 : " + result1);

//example 2
Map<Integer, String> result2 = list.stream().collect(
    Collectors.toMap(x -> x.getId(), x -> x.getName()));

Code copied from:
https://www.mkyong.com/java8/java-8-convert-list-to-map/

Onrush answered 25/5, 2017 at 9:50 Comment(0)
S
2

Many solutions come to mind, depending on what you want to achive:

Every List item is key and value

for( Object o : list ) {
    map.put(o,o);
}

List elements have something to look them up, maybe a name:

for( MyObject o : list ) {
    map.put(o.name,o);
}

List elements have something to look them up, and there is no guarantee that they are unique: Use Googles MultiMaps

for( MyObject o : list ) {
    multimap.put(o.name,o);
}

Giving all the elements the position as a key:

for( int i=0; i<list.size; i++ ) {
    map.put(i,list.get(i));
}

...

It really depends on what you want to achive.

As you can see from the examples, a Map is a mapping from a key to a value, while a list is just a series of elements having a position each. So they are simply not automatically convertible.

Scirrhus answered 9/11, 2010 at 20:49 Comment(3)
But we can consider List Element position as key and put their value in map, is this a good solution ?Wordsmith
AFAIK yes! There is no function in the JDK that does that automatically, so you have to roll your own.Scirrhus
is it possible to do the last version (using array index as map key) with java 8 streams?Sharpeyed
V
2

Here's a little method I wrote for exactly this purpose. It uses Validate from Apache Commons.

Feel free to use it.

/**
 * Converts a <code>List</code> to a map. One of the methods of the list is called to retrive
 * the value of the key to be used and the object itself from the list entry is used as the
 * objct. An empty <code>Map</code> is returned upon null input.
 * Reflection is used to retrieve the key from the object instance and method name passed in.
 *
 * @param <K> The type of the key to be used in the map
 * @param <V> The type of value to be used in the map and the type of the elements in the
 *            collection
 * @param coll The collection to be converted.
 * @param keyType The class of key
 * @param valueType The class of the value
 * @param keyMethodName The method name to call on each instance in the collection to retrieve
 *            the key
 * @return A map of key to value instances
 * @throws IllegalArgumentException if any of the other paremeters are invalid.
 */
public static <K, V> Map<K, V> asMap(final java.util.Collection<V> coll,
        final Class<K> keyType,
        final Class<V> valueType,
        final String keyMethodName) {

    final HashMap<K, V> map = new HashMap<K, V>();
    Method method = null;

    if (isEmpty(coll)) return map;
    notNull(keyType, Messages.getString(KEY_TYPE_NOT_NULL));
    notNull(valueType, Messages.getString(VALUE_TYPE_NOT_NULL));
    notEmpty(keyMethodName, Messages.getString(KEY_METHOD_NAME_NOT_NULL));

    try {
        // return the Method to invoke to get the key for the map
        method = valueType.getMethod(keyMethodName);
    }
    catch (final NoSuchMethodException e) {
        final String message =
            String.format(
                    Messages.getString(METHOD_NOT_FOUND),
                    keyMethodName,
                    valueType);
        e.fillInStackTrace();
        logger.error(message, e);
        throw new IllegalArgumentException(message, e);
    }
    try {
        for (final V value : coll) {

            Object object;
            object = method.invoke(value);
            @SuppressWarnings("unchecked")
            final K key = (K) object;
            map.put(key, value);
        }
    }
    catch (final Exception e) {
        final String message =
            String.format(
                    Messages.getString(METHOD_CALL_FAILED),
                    method,
                    valueType);
        e.fillInStackTrace();
        logger.error(message, e);
        throw new IllegalArgumentException(message, e);
    }
    return map;
}
Visakhapatnam answered 14/7, 2011 at 14:0 Comment(0)
C
2

You can leverage the streams API of Java 8.

public class ListToMap {

  public static void main(String[] args) {
    List<User> items = Arrays.asList(new User("One"), new User("Two"), new User("Three"));

    Map<String, User> map = createHashMap(items);
    for(String key : map.keySet()) {
      System.out.println(key +" : "+map.get(key));
    }
  }

  public static Map<String, User> createHashMap(List<User> items) {
    Map<String, User> map = items.stream().collect(Collectors.toMap(User::getId, Function.identity()));
    return map;
  }
}

For more details visit: http://codecramp.com/java-8-streams-api-convert-list-map/

Crannog answered 9/6, 2017 at 4:33 Comment(0)
A
0

I like Kango_V's answer, but I think it's too complex. I think this is simpler - maybe too simple. If inclined, you could replace String with a Generic marker, and make it work for any Key type.

public static <E> Map<String, E> convertListToMap(Collection<E> sourceList, ListToMapConverterInterface<E> converterInterface) {
    Map<String, E> newMap = new HashMap<String, E>();
    for( E item : sourceList ) {
        newMap.put( converterInterface.getKeyForItem( item ), item );
    }
    return newMap;
}

public interface ListToMapConverterInterface<E> {
    public String getKeyForItem(E item);
}

Used like this:

        Map<String, PricingPlanAttribute> pricingPlanAttributeMap = convertListToMap( pricingPlanAttributeList,
                new ListToMapConverterInterface<PricingPlanAttribute>() {

                    @Override
                    public String getKeyForItem(PricingPlanAttribute item) {
                        return item.getFullName();
                    }
                } );
Anchoress answered 20/9, 2011 at 15:42 Comment(0)
N
0

Apache Commons MapUtils.populateMap

If you don't use Java 8 and you don't want to use a explicit loop for some reason, try MapUtils.populateMap from Apache Commons.

MapUtils.populateMap

Say you have a list of Pairs.

List<ImmutablePair<String, String>> pairs = ImmutableList.of(
    new ImmutablePair<>("A", "aaa"),
    new ImmutablePair<>("B", "bbb")
);

And you now want a Map of the Pair's key to the Pair object.

Map<String, Pair<String, String>> map = new HashMap<>();
MapUtils.populateMap(map, pairs, new Transformer<Pair<String, String>, String>() {

  @Override
  public String transform(Pair<String, String> input) {
    return input.getKey();
  }
});

System.out.println(map);

gives output:

{A=(A,aaa), B=(B,bbb)}

That being said, a for loop is maybe easier to understand. (This below gives the same output):

Map<String, Pair<String, String>> map = new HashMap<>();
for (Pair<String, String> pair : pairs) {
  map.put(pair.getKey(), pair);
}
System.out.println(map);
Nonmetallic answered 27/3, 2018 at 2:46 Comment(0)
E
0

If you use Kotlin, there is an example:

listOf("one", "two").mapIndexed { i, it -> i to it }.toMap()
Evonneevonymus answered 5/9, 2022 at 11:39 Comment(0)
R
0
public class EmployeeDetailsFetchListToMap {
  public static void main(String[] args) {
    List<EmployeeDetailsFetch> list = new ArrayList<>();
    list.add(new EmployeeDetailsFetch(1L, "vinay", 25000F));
    list.add(new EmployeeDetailsFetch(2L, "kohli", 5000000F));
    list.add(new EmployeeDetailsFetch(3L, "dhoni", 20000000F));

    //adding id as key and map of id and student name
    Map<Long, Map<Long, String>> map1 = list.stream()
        .collect(
            Collectors.groupingBy(
                EmployeeDetailsFetch::getEmpId,
                Collectors.toMap(
                    EmployeeDetailsFetch::getEmpId,
                    EmployeeDetailsFetch::getEmployeeName
                )
            )
        );
    System.out.println(map1);

    //converting list into map of Student
    //Adding id as Key and Value as Student into a map
    Map<Long, EmployeeDetailsFetch> map = list.stream()
        .collect(
            Collectors.toMap(
                EmployeeDetailsFetch::getEmpId, 
                EmployeeDetailsFetch -> EmployeeDetailsFetch
            )
        );

    for(Map.Entry<Long, EmployeeDetailsFetch> m : map.entrySet()) {
      System.out.println("key :" + m.getKey() + "  Value : " + m.getValue());
    }
  }
}
Rencontre answered 11/10, 2022 at 6:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.