990

I want to translate a List of objects into a Map using Java 8's streams and lambdas.

This is how I would write it in Java 7 and below.

private Map<String, Choice> nameMap(List<Choice> choices) {
        final Map<String, Choice> hashMap = new HashMap<>();
        for (final Choice choice : choices) {
            hashMap.put(choice.getName(), choice);
        }
        return hashMap;
}

I can accomplish this easily using Java 8 and Guava but I would like to know how to do this without Guava.

In Guava:

private Map<String, Choice> nameMap(List<Choice> choices) {
    return Maps.uniqueIndex(choices, new Function<Choice, String>() {

        @Override
        public String apply(final Choice input) {
            return input.getName();
        }
    });
}

And Guava with Java 8 lambdas.

private Map<String, Choice> nameMap(List<Choice> choices) {
    return Maps.uniqueIndex(choices, Choice::getName);
}
Vitruvie
  • 2,281
  • 16
  • 24
Tom Cammann
  • 14,873
  • 4
  • 32
  • 47

22 Answers22

1452

Based on Collectors documentation it's as simple as:

Map<String, Choice> result =
    choices.stream().collect(Collectors.toMap(Choice::getName,
                                              Function.identity()));
Alexis C.
  • 82,826
  • 18
  • 154
  • 166
zapl
  • 60,293
  • 10
  • 115
  • 141
  • 170
    As a side note, even after Java 8, the JDK still can't compete in a brevity contest. The Guava alternative looks so much readable: `Maps.uniqueIndex(choices, Choice::getName)`. – Bogdan Calmac Mar 03 '15 at 17:59
  • 4
    Using (statically imported) `Seq` from the [JOOL](https://github.com/jOOQ/jOOL) library (which I'd recommend to anyone using Java 8), you can also improve the brevity with: `seq(choices).toMap(Choice::getName)` – lukens Mar 18 '17 at 08:45
  • 7
    Are there any benefits from using Function.identity? I mean, it -> it is shorter – shabunc May 30 '17 at 12:49
  • 10
    @shabunc I don't know of any benefit and actually use `it -> it` myself. `Function.identity()` is used here mostly because it's used in the referenced documentation and that was all I knew about lambdas at the time of writing – zapl May 30 '17 at 12:57
  • 13
    @zapl, oh, actually it turns out there are reasons behind this - https://stackoverflow.com/questions/28032827/java-8-lambdas-function-identity-or-t-t – shabunc May 30 '17 at 13:39
  • you can see my answer. how to convert list to map using generics and inversion of control: https://stackoverflow.com/a/51334242/2590960 – grep Jul 14 '18 at 00:30
  • Please check the answer by @Ulises if you need to consider duplicate Keys. Need to use Map> instead of a Map – Chinmoy Oct 26 '18 at 04:23
  • hot to access for example to Choice::getOtherObject::geName ? please – Fernando Pie Jan 24 '19 at 20:52
  • What's the performance difference between Guava and using streams? – Stephane Grenier Jun 24 '19 at 20:25
  • @BogdanCalmac & @lukens: While Guava and jOOλ are nice choices, just using JDK with static imports alone does make it a lot shorter already: `.collect(toMap(Choice::getName, identity()))`. – Jacob van Lingen Oct 15 '20 at 11:58
326

If your key is NOT guaranteed to be unique for all elements in the list, you should convert it to a Map<String, List<Choice>> instead of a Map<String, Choice>

Map<String, List<Choice>> result =
 choices.stream().collect(Collectors.groupingBy(Choice::getName));
stkent
  • 18,470
  • 14
  • 80
  • 99
Ulises
  • 7,547
  • 2
  • 24
  • 26
  • 76
    This actually gives you Map> which deals with the possibility of non-unique keys, but isn't what the OP requested. In Guava, Multimaps.index(choices, Choice::getName) is probably a better option if this is what you want anyhow. – Richard Nichols Oct 29 '14 at 04:41
  • or rather use Guava's Multimap which comes quite handy in scenarios where same key maps to multiple values. There are various utility methods readily available in Guava to use such data structures rather than creating a Map> – Neeraj B. Jul 06 '18 at 14:03
  • 1
    @RichardNichols why is the guava `Multimaps` method a better option? It can be an inconvenience since it doesn't return a `Map` Object. – theyuv Nov 01 '18 at 13:47
  • 1
    @RichardNichols it might no be what OP requested but I was looking for exactly this and am so happy that this answer exists! – ElectRocnic Jan 26 '21 at 11:19
169

Use getName() as the key and Choice itself as the value of the map:

Map<String, Choice> result =
    choices.stream().collect(Collectors.toMap(Choice::getName, c -> c));
Oleksandr Pyrohov
  • 13,194
  • 4
  • 51
  • 85
Ole
  • 29,797
  • 32
  • 110
  • 232
  • 20
    Please write some description so that user can understand. – Mayank Jain Feb 28 '15 at 07:38
  • 4
    It's really too bad there isn't more details here, because I like this answer best. – MadConan Nov 24 '15 at 13:50
  • `Collectors.toMap(Choice::getName,c->c)` (2 chars shorter) – Ferrybig Nov 26 '15 at 13:43
  • 9
    It's equal to `choices.stream().collect(Collectors.toMap(choice -> choice.getName(),choice -> choice));` First function for key, second function for value – waterscar Jan 06 '16 at 07:29
  • 18
    I know how easy it is to see and understand `c -> c` but `Function.identity()` carries more semantic information. I usually use a static import so that i can just use `identity()` – Hank D Apr 09 '16 at 21:06
29

Here's another one in case you don't want to use Collectors.toMap()

Map<String, Choice> result =
   choices.stream().collect(HashMap<String, Choice>::new, 
                           (m, c) -> m.put(c.getName(), c),
                           (m, u) -> {});
Emre Colak
  • 764
  • 7
  • 14
  • 1
    Which is better to use then `Collectors.toMap()` or our own HashMap as you showed in above example? – Swapnil Gangrade Jul 18 '16 at 14:22
  • 1
    This example provided an example for how to place something else in the map. I wanted a value not provided by a method call. Thanks! – th3morg Apr 21 '17 at 01:51
  • 2
    The third argument function is not correct. There you should provide some function to merge two Hashmaps, something like Hashmap::putAll – jesantana Jul 21 '17 at 08:34
27

Most of the answers listed, miss a case when the list has duplicate items. In that case there answer will throw IllegalStateException. Refer the below code to handle list duplicates as well:

public Map<String, Choice> convertListToMap(List<Choice> choices) {
    return choices.stream()
        .collect(Collectors.toMap(Choice::getName, choice -> choice,
            (oldValue, newValue) -> newValue));
  }
Sahil Chhabra
  • 7,481
  • 4
  • 53
  • 53
21

One more option in simple way

Map<String,Choice> map = new HashMap<>();
choices.forEach(e->map.put(e.getName(),e));
Renukeswar
  • 547
  • 1
  • 6
  • 14
20

For example, if you want convert object fields to map:

Example object:

class Item{
        private String code;
        private String name;

        public Item(String code, String name) {
            this.code = code;
            this.name = name;
        }

        //getters and setters
    }

And operation convert List To Map:

List<Item> list = new ArrayList<>();
list.add(new Item("code1", "name1"));
list.add(new Item("code2", "name2"));

Map<String,String> map = list.stream()
     .collect(Collectors.toMap(Item::getCode, Item::getName));
Sahil Chhabra
  • 7,481
  • 4
  • 53
  • 53
Piotr R
  • 3,088
  • 13
  • 19
12

If you don't mind using 3rd party libraries, AOL's cyclops-react lib (disclosure I am a contributor) has extensions for all JDK Collection types, including List and Map.

ListX<Choices> choices;
Map<String, Choice> map = choices.toMap(c-> c.getName(),c->c);
John McClean
  • 4,767
  • 1
  • 20
  • 30
11

You can create a Stream of the indices using an IntStream and then convert them to a Map :

Map<Integer,Item> map = 
IntStream.range(0,items.size())
         .boxed()
         .collect(Collectors.toMap (i -> i, i -> items.get(i)));
Vikas Suryawanshi
  • 494
  • 1
  • 5
  • 12
10

I was trying to do this and found that, using the answers above, when using Functions.identity() for the key to the Map, then I had issues with using a local method like this::localMethodName to actually work because of typing issues.

Functions.identity() actually does something to the typing in this case so the method would only work by returning Object and accepting a param of Object

To solve this, I ended up ditching Functions.identity() and using s->s instead.

So my code, in my case to list all directories inside a directory, and for each one use the name of the directory as the key to the map and then call a method with the directory name and return a collection of items, looks like:

Map<String, Collection<ItemType>> items = Arrays.stream(itemFilesDir.listFiles(File::isDirectory))
.map(File::getName)
.collect(Collectors.toMap(s->s, this::retrieveBrandItems));
iZian
  • 295
  • 3
  • 7
9

I will write how to convert list to map using generics and inversion of control. Just universal method!

Maybe we have list of Integers or list of objects. So the question is the following: what should be key of the map?

create interface

public interface KeyFinder<K, E> {
    K getKey(E e);
}

now using inversion of control:

  static <K, E> Map<K, E> listToMap(List<E> list, KeyFinder<K, E> finder) {
        return  list.stream().collect(Collectors.toMap(e -> finder.getKey(e) , e -> e));
    }

For example, if we have objects of book , this class is to choose key for the map

public class BookKeyFinder implements KeyFinder<Long, Book> {
    @Override
    public Long getKey(Book e) {
        return e.getPrice()
    }
}
grep
  • 4,615
  • 10
  • 48
  • 96
7

I use this syntax

Map<Integer, List<Choice>> choiceMap = 
choices.stream().collect(Collectors.groupingBy(choice -> choice.getName()));
user2069723
  • 157
  • 2
  • 2
5
Map<String, Set<String>> collect = Arrays.asList(Locale.getAvailableLocales()).stream().collect(Collectors
                .toMap(l -> l.getDisplayCountry(), l -> Collections.singleton(l.getDisplayLanguage())));
Tunaki
  • 116,530
  • 39
  • 281
  • 370
Kumar Abhishek
  • 2,448
  • 25
  • 26
4

This can be done in 2 ways. Let person be the class we are going to use to demonstrate it.

public class Person {

    private String name;
    private int age;

    public String getAge() {
        return age;
    }
}

Let persons be the list of Persons to be converted to the map

1.Using Simple foreach and a Lambda Expression on the List

Map<Integer,List<Person>> mapPersons = new HashMap<>();
persons.forEach(p->mapPersons.put(p.getAge(),p));

2.Using Collectors on Stream defined on the given List.

 Map<Integer,List<Person>> mapPersons = 
           persons.stream().collect(Collectors.groupingBy(Person::getAge));
raja emani
  • 107
  • 4
4

It's possible to use streams to do this. To remove the need to explicitly use Collectors, it's possible to import toMap statically (as recommended by Effective Java, third edition).

import static java.util.stream.Collectors.toMap;

private static Map<String, Choice> nameMap(List<Choice> choices) {
    return choices.stream().collect(toMap(Choice::getName, it -> it));
}
Konrad Borowski
  • 9,885
  • 2
  • 50
  • 68
3

Here is solution by StreamEx

StreamEx.of(choices).toMap(Choice::getName, c -> c);
user_3380739
  • 1,405
  • 10
  • 12
3
Map<String,Choice> map=list.stream().collect(Collectors.toMap(Choice::getName, s->s));

Even serves this purpose for me,

Map<String,Choice> map=  list1.stream().collect(()-> new HashMap<String,Choice>(), 
            (r,s) -> r.put(s.getString(),s),(r,s) -> r.putAll(s));
Rajeev Akotkar
  • 1,129
  • 2
  • 17
  • 36
3

If every new value for the same key name has to be overridden:

public Map < String, Choice > convertListToMap(List < Choice > choices) {
    return choices.stream()
        .collect(Collectors.toMap(Choice::getName,
            Function.identity(),
            (oldValue, newValue) - > newValue));
}

If all choices have to be grouped in a list for a name:

public Map < String, Choice > convertListToMap(List < Choice > choices) {
    return choices.stream().collect(Collectors.groupingBy(Choice::getName));
}
Ihor Sakailiuk
  • 3,757
  • 3
  • 17
  • 22
Vaneet Kataria
  • 417
  • 3
  • 12
1
List<V> choices; // your list
Map<K,V> result = choices.stream().collect(Collectors.toMap(choice::getKey(),choice));
//assuming class "V" has a method to get the key, this method must handle case of duplicates too and provide a unique key.
Dino
  • 6,207
  • 8
  • 28
  • 62
vaibhav
  • 41
  • 2
  • 6
1

Another possibility only present in comments yet:

Map<String, Choice> result =
choices.stream().collect(Collectors.toMap(c -> c.getName(), c -> c)));

Useful if you want to use a parameter of a sub-object as Key:

Map<String, Choice> result =
choices.stream().collect(Collectors.toMap(c -> c.getUser().getName(), c -> c)));
L. G.
  • 9,087
  • 7
  • 47
  • 73
0

As an alternative to guava one can use kotlin-stdlib

private Map<String, Choice> nameMap(List<Choice> choices) {
    return CollectionsKt.associateBy(choices, Choice::getName);
}
Frank Neblung
  • 2,395
  • 12
  • 27
-1
String array[] = {"ASDFASDFASDF","AA", "BBB", "CCCC", "DD", "EEDDDAD"};
    List<String> list = Arrays.asList(array);
    Map<Integer, String> map = list.stream()
            .collect(Collectors.toMap(s -> s.length(), s -> s, (x, y) -> {
                System.out.println("Dublicate key" + x);
                return x;
            },()-> new TreeMap<>((s1,s2)->s2.compareTo(s1))));
    System.out.println(map);

Dublicate key AA {12=ASDFASDFASDF, 7=EEDDDAD, 4=CCCC, 3=BBB, 2=AA}

Ajay Kumar
  • 3,408
  • 30
  • 35