- Java 8 list to map with stream
- 6 Answers 6
- How to convert List into Map
, with Java 8 streams and custom List and Map suppliers? - 3 Answers 3
- Java List to Map convertion
- 2 Answers 2
- Examples of Converting List to Map using Streams
- Prepare the Input List
- Convert List to Simple Map using Streams – toMap() Collector
- Convert List to Map Using Streams – toMap() Collectors
- How to group by a Field using Stream – groupingBy() Collector
- Summary
- One thought on “ Examples of Converting List to Map using Streams ”
Java 8 list to map with stream
doing a little research for this question, I learned that there is amazingly no zip in java 8 streams.
@njzk2, that’s simply because you cannot parallelize zipped stream. Having random access sources (for example, two ArrayList ) it’s not very difficult to zip them via IntStream.range(0,list1.size()).mapToObj(idx -> doSomethingWith(list1.get(idx), list2.get(idx))) and the result will be parallel-friendly
6 Answers 6
You can create a Stream of the indices using an IntStream and then convert them to a Map :
Map map = IntStream.range(0,items.size()) .boxed() .collect(Collectors.toMap (i -> i, i -> items.get(i)));
that stops working as soon as items is no longer a List . And is highly inefficient if items is, for example, a LinkedList instead of an ArrayList .
One more solution just for completeness is to use custom collector:
public static Collector> toMap() < return Collector.of(HashMap::new, (map, t) ->map.put(map.size(), t), (m1, m2) -> < int s = m1.size(); m2.forEach((k, v) ->m1.put(k+s, v)); return m1; >); >
Map map = items.stream().collect(toMap());
This solution is parallel-friendly and does not depend on the source (you can use list without random access or Files.lines() or whatever).
this works iff a/ the combiner is guaranteed to be called with m1 and m2 in the right order b/ each accumulating map is called on a continuous sequence of items. This breaks if for example, odd values are accumulated in a map and even values in another one. I haven’t found any source that suggests this could not happen.
@njzk2, nevertheless this could not happen if your stream is ordered. This way all the existing collectors (like toList() ) actually work.
I guess that makes sense. I will be researching into how collectors guarantee the order of the stream after parallelization happens.
@njzk2, the Collector contract is described in API docs. I fulfill the contract. When correct map and new element is passed to the accumulator, it produces new correct map. When two correct maps are passed to combiner, it produces new correct map. Just fulfill the contract, and you’ll get the correct result. That’s the beauty of interfaces.
thanks. It appears that the stream is partitioned in substrings, not subsequences, so that works indeed!
Don’t feel like you have to do everything in/with the stream. I would just do:
AtomicInteger index = new AtomicInteger(); items.stream().collect(Collectors.toMap(i -> index.getAndIncrement(), i -> i));
As long as you don’t parallelise the stream this will work and it avoids potentially expensive and/or problematic (in the case of duplicates) get() and indexOf() operations.
(You cannot use a regular int variable in place of the AtomicInteger because variables used from outside a lambda expression must be effectively final. Note that when uncontested (as in this case), AtomicInteger is very fast and won’t pose a performance problem. But if it worries you you can use a non-thread-safe counter.)
@Holger No. I call List.get() potentially expensive. AtomicInteger is O(1), List.get() could be anything up to O(n).
Only if you use LinkedList which is not really useful. On the other hand, AtomicInteger being O(1) isn’t really relevant when it comes to the hidden cost of thread safety in an operation that you admit yourself, doesn’t work in parallel. If you start your answer with “Don’t feel like you have to do everything in/with the stream”, why don’t you provide a stream-less alternative, like a straight-forward loop? That would be better than presenting a discouraged stream usage…
@Holger The OP doesn’t specify the List implementation. You seem to be prejudiced against LinkedList , but of course in reality there is nothing wrong with it and the List could easily be one, or possibly even another implementation which is even more expensive. Why second guess it? This way will always be the fastest.
I’m not prejudicing against LinkedList as it already exists for more than fifteen years now, which is enough time to determine that it is not useful in real life. The theoretical advantage is only one operation, inserting at an arbitrary index, but since it has to allocate memory for that and update half a dozen node references, that advantage doesn’t really materialize. It would require very large lists to outplay an ArrayList , however for large lists, the insane memory overhead of LinkedList will counter-act it. LinkedList wins only in O(…) comparisons which ignore memory effects
How to convert List into Map, with Java 8 streams and custom List and Map suppliers?
Question: Is there an easier, less verbose, or more efficient way of doing it? For example, something like this (which doesn’t work):
return strings.stream() .collect(Collectors.toMap( String::length, ArrayList::new, HashMap::new));
3 Answers 3
You could have the following:
public Map> getMap(List strings)
The collector groupingBy(classifier, mapFactory, downstream) can be used to specify which type of map is wanted, by passing it a supplier of the wanted map for the mapFactory . Then, the downstream collector, which is used to collect elements grouped to the same key, is toCollection(collectionFactory) , which enables to collect into a collection obtained from the given supplier.
This makes sure that the map returned is a HashMap and that the lists, in each value, are ArrayList . Note that if you want to return specific implementations of map and collection, then you most likely want the method to return those specific types as well, so you can use their properties.
If you only want to specify a collection supplier, and keep groupingBy default map, you can just omit the supplier in the code above and use the two arguments overload:
public Map> getMap(List strings)
As a side-note, you could have a generic method for that:
public , M extends Map> M getMap(List list, Function classifier, Supplier mapSupplier, Supplier collectionSupplier)
The advantage with this declaration is that you can now use it to have specific HashMap of ArrayList s as result, or LinkedHashMap of LinkedLists s, if the caller wishes it:
HashMap> m = getMap(Arrays.asList("foo", "bar", "toto"), String::length, HashMap::new, ArrayList::new); LinkedHashMap> m2 = getMap(Arrays.asList("foo", "bar", "toto"), String::length, LinkedHashMap::new, LinkedList::new);
but, at that point, it may be simpler to directly use the groupingBy in the code.
Java List to Map convertion
because I have a list of Strings, and I’d like to to create a Map, where the key is the string of the list, and the value is Integer (a zero). My goal is, to counting the elements of String list (later in my code). I know it is easy to convert it, in the «old» way;
Map namesMap = new HasMap<>(); for(String str: names)
Just change 0 to name -> 0 : Map
2 Answers 2
As already noted, the parameters to Collectors.toMap have to be functions, so you have to change 0 to name -> 0 (you can use any other parameter name instead of name ).
Note, however, that this will fail if there are duplicates in names , as that will result in duplicate keys in the resulting map. To fix this, you could pipe the stream through Stream.distinct first:
Map namesMap = names.stream().distinct() .collect(Collectors.toMap(s -> s, s -> 0));
Or don’t initialize those defaults at all, and use getOrDefault or computeIfAbsent instead:
int x = namesMap.getOrDefault(someName, 0); int y = namesMap.computeIfAbsent(someName, s -> 0);
Or, if you want to get the counts of the names, you can just use Collectors.groupingBy and Collectors.counting :
Map counts = names.stream().collect( Collectors.groupingBy(s -> s, Collectors.counting()));
Examples of Converting List to Map using Streams
Learn different ways of using Java Streams API to Convert a List of elements to a Map.
Prepare the Input List
Let’s consider you have a User class and a List of users you want to convert to Map.
public class User < private Long id; private String name; private String lastName; private int age; // Getter, Setter, and Constructors >
Code language: Java (java)
Create a List of User objects
List users = List.of( new User(1l, "Bob", "Taylor", 30), new User(2l, "Tom", "Richard", 32), new User(3l, "Bob", "Wills", 31), new User(4l, "Nick", "Jackson", 29), new User(5l, "john", "Anderson", 31), new User(6l, "Tom", "Taylor", 30), new User(7l, "Bob", "Mills", 29) );
Code language: Java (java)
In the below sections, we will use Streams to convert the above List into different types of Maps.
Convert List to Simple Map using Streams – toMap() Collector
The Java Stream Collectors.toMap() is a convenient method for creating maps. The method accepts two Functions – the first for mapping the key and the other for mapping the value.
Here, were are streaming through a List of User objects and creating a Map of their Id to the last names.
Map map = users.stream() .collect (Collectors.toMap(User::getId, User::getLastName));
Code language: Java (java)
Here, instead of providing implementations of the Function interface, we use Method reference notation to simplify the toMap() invocation.
As expected, the resulting map has the correct key and value mapping.
1=Taylor 2=Richard 3=Wills 4=Jackson 5=Anderson 6=Taylor 7=Mills
Code language: plaintext (plaintext)
Convert List to Map Using Streams – toMap() Collectors
We saw how to convert a List to a simple map. However, instead of putting just the last name into the value, we may want to store the entire User object as a value. To do that, we can use Function.identity() function.
Let’s group the users into a Map by their Id fields.
List ——-> Map
Map map = users.stream() .collect (Collectors.toMap(User::getId, Function.identity()));
Code language: Java (java)
Now, we get a Map of the user Id to the user instance.
1=[id: 1, name: Bob, last name: Taylor, age: 30] 2=[id: 2, name: Tom, last name: Richard, age: 32] 3=[id: 3, name: Bob, last name: Wills, age: 31] 4=[id: 4, name: Nick, last name: Jackson, age: 29] 5=[id: 5, name: john, last name: Anderson, age: 31] 6=[id: 6, name: Tom, last name: Taylor, age: 30] 7=[id: 7, name: Bob, last name: Mills, age: 29]
Code language: plaintext (plaintext)
How to group by a Field using Stream – groupingBy() Collector
Our previously created Maps had a single value. That is because we grouped them using a unique key. However, some keys can have multiple values – for example, grouping a list of users based on their first names. To do so, we can use the groupingBy() collector that takes the key mapper. The value is a list of grouped elements.
Let’s convert a List of User objects to a Map of user names to a list of users.
Map> groupedByName = users.stream() .collect (Collectors.groupingBy(User::getName));
Code language: Java (java)
The outcome is as expected.
Tom=[[id: 2, name: Tom, last name: Richard, age: 32], [id: 6, name: Tom, last name: Taylor, age: 30]] Bob=[[id: 1, name: Bob, last name: Taylor, age: 30], [id: 3, name: Bob, last name: Wills, age: 31], [id: 7, name: Bob, last name: Mills, age: 29]] Nick=[[id: 4, name: Nick, last name: Jackson, age: 29]] john=[[id: 5, name: john, last name: Anderson, age: 31]]
Code language: plaintext (plaintext)
Summary
This example-oriented tutorial discussed converting Java Lists to Maps using Java Streams API. It first demonstrated toMap() function to convert a List of objects to a Map of a specific key and value pairs. Then it used the Function.identity() with the toMap() collector to create a Map of a particular key and the element as value. Lastly, it demonstrated groupingBy() collector to create a Map of a non-unique key and element list as the value.
One thought on “ Examples of Converting List to Map using Streams ”
Hello there, My name is George, and I was wondering if you would like to have your website amitph.com promoted as a resource on my blog georgemartjr.com ? We are updating our broken link resources to include up to date resources for our readers. Our resource links are manually approved as a do follow link.
If you are interested in having your site included as a resource on our blog, please let me know. Thanks for your consideration,
George