How to collect data in map from stream using Collectors.map() || Guide to collect(Collectors.map())

Darshan Dalwadi
5 min readDec 27, 2023

--

👋Hello Readers, Greetings for the day.!

In this article, we will understand that, how we can collect data as a map from the stream with the help of collect(Collectors.toMap());

After reading this article you will definitely get idea about how to collect data in map() from stream.

📌Purpose of stream (Just To Remind):

A stream represents a sequence of objects/elements and it supports many intermediate and terminal operations over a collection. In last you will get your desired output stream or you can also collect them into a specific data structure like List, Set, Map using Collectors.

Keep in mind that, stream are generally used to process the elements, so its not altering the actual objects, but yes with the help of stream operations you can create a new data structure or stream with the altered objects. You can check below code snippet for the same.

// Streams are not modifying existing list, but it just allows to create another
// stream/data structure with effect
List<String> originalList = Arrays.asList("someone", "anyone", "noone");
Set<String> resultingList = originalList.stream().map(value -> value.toUpperCase())
.collect(Collectors.toSet());
System.out.println("Original List : " + originalList);
System.out.println("Converted Set : " + resultingList);

=============================================================
O/P:
Original : [someone, anyone, noone]
Converted : [SOMEONE, ANYONE, NOONE]

🔥Let’s jump on our actual Topic:

Stream API provides 3 variants of map() method to collect data into the Map. Check the below syntax of all the 3 variants:

//1st Variant (KeyMapper, ValueMapper)
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper) {
return new CollectorImpl<>(HashMap::new,
uniqKeysMapAccumulator(keyMapper, valueMapper),
uniqKeysMapMerger(),
CH_ID);
}

//2nd Variant (KeyMapper, ValueMapper, MergerFunction)
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction) {
return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}

//3rd Variant (KeyMapper, ValueMapper, MergerFunction, TypeOfResultingMap)
public static <T, K, U, M extends Map<K, U>>
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapFactory) {
BiConsumer<M, T> accumulator
= (map, element) -> map.merge(keyMapper.apply(element),
valueMapper.apply(element), mergeFunction);
return new CollectorImpl<>(mapFactory, accumulator, mapMerger(mergeFunction), CH_ID);
}

Define one list which we will use during the article:

// Consider an Employee POJO with having id, Name, DepartmentName fields.
public static List<Employee> getEmployeeList() {

List<Employee> employees = new ArrayList<Employee>();
employees.add(new Employee(101, "Darshan", "IT"));
employees.add(new Employee(102, "Kishan", "IT"));
employees.add(new Employee(103, "Vimal", "Management"));
employees.add(new Employee(104, "Priyansh", "Management"));
employees.add(new Employee(105, "Dipika", "HR"));
employees.add(new Employee(106, "Kangna", "HR"));

return employees;
}

Let’s start with First Variant:

In first variant we need to pass two arguments (Functions), one argument for key (KeyMapper) and second argument is for Value (ValueMapper). Both arguments are functions. You can use Lambda and Method refence to write/pass them in map() method. Consider a below example.

Suppose you’ve a requirement that you need to get Map from list of products in such way that, ProductId will be the key and ProductName will be the value. Here same scenario you can assume with our example EmpId as key, and EmpName as value, lets consider below code snippet:

// 1st variant(KeyMapper,valueMapper)
List<Employee> employees = Employee.getEmployeeList();
// you can also use "Employee -> Employee.getName()" instead of
// Employee::getName.
Map<Integer, String> empMap = employees.stream().collect(Collectors.toMap(Employee::getEmpId, Employee::getName));
System.out.println("Map with EmpId and EmpName : " + empMap);

==================================================
o/p:
Map with EmpId and EmpName : {101=Darshan, 102=Kishan, 103=Vimal, 104=Priyansh, 105=Dipika, 106=Kangna}

Move to the Second Variant:

Consider the scenario that your list may have duplicate entries, or duplicate names or assume there is a duplicate email-Ids, so that will resulting in a bad data output, or may be loss of data. To demonstrate we need to make duplicate entry in our existing employee list.

// Add duplicate entry for the Id 101
employees.add(new Employee(101, "DD", new Department(201, "IT"))); // Add duplicate employee

// 2nd variant
// obj1, obj2 represents the value object (which is empName)
Map<Integer, String> empMapWithSecondVariant = employees.stream()
.collect(Collectors.toMap(Employee::getEmpId, Employee::getName, (obj1, obj2) -> {
throw new IllegalStateException("Duplicate Entry .!");
}));

System.out.println("Resulting Map : " + empMapWithSecondVariant);

==================================================
o/p:
Exception in thread "main" java.lang.IllegalStateException: Duplicate Entry .!

So, as above you can throw an exception if you don’t want to process for duplicates (key is duplicate). There is another option, you can also do some processing or can select any one of them as a result to overcome the situation, take a look at below snippet:

// Add duplicate entry for the Id 101
employees.add(new Employee(101, "DD", new Department(201, "IT"))); // Add duplicate employee

// 2nd variant
// obj1, obj2 represents the value object (which is empName)
Map<Integer, String> empMapWithSecondVariant = employees.stream()
.collect(Collectors.toMap(Employee::getEmpId, Employee::getName,
(obj1, obj2) -> {
return obj2; // You can aslo use any other string value here
}
));

System.out.println(" O/p Of second variant : " + empMapWithSecondVariant);

==================================================
o/p:
O/p Of second variant : {101=DD, 102=Kishan, 103=Vimal, 104=Priyansh, 105=Dipika, 106=Kangna}

You can see the output, “Darshan” is replaced with string “DD”, you can also pass or return any other string. The return value should match with the type of ValueMapper’s input type.

Let’s jump to the Third Variant:

Consider the scenario where you want to store your result into the desired Map type like LinkedHashMap, TreeMap, etc…

Third variant accepts 4 arguments, first is Key-mapper, second is Value-mapper, third is Merger function, and fourth is the mapSupplier -> The mapSupplier function specifies the specific implementation of the Map you want to use resulting/final Map.

//3rd variant(KeyMapper,ValueMapper,MergerFunction,TypeOfResultMap)
Map<Integer, String> empMapWithNewType = employees.stream()
.collect(Collectors.toMap(Employee::getEmpId, Employee::getName,
(o1, o2) -> o1, HashMap::new));

System.out.println("Resulting Map With Desired Map Type : " + empMapWithNewType);

==================================================
o/p:
Emp Map With Desired Map Type :
{101=Darshan, 102=Kishan, 103=Vimal, 104=Priyansh, 105=Dipika, 106=Kangna}

Note: HashMap is a default resulting type if you are not passing the fourth argument.

The problem here is, suppose you want to sort the list, and want to convert the same result into the map - without affecting your sorted order, you need to use LinkedHashMap or TreeMap, because by default its using HashMap which orders elements based on their hashes, so in last you will not get your result in the sorted order. Don’t forget to check output, it is alphabetically sorted by name.

// 3rd variant - pass TreeMap as 4th argument
Map<Integer, String> empMapInSortedOrder = employees.stream().sorted()
.collect(Collectors.toMap(Employee::getEmpId, Employee::getName,
(o1, o2) -> o1, TreeMap::new));

System.err.println("Emp Map In Sorted order : " + empMapInSortedOrder);

==================================================
o/p:
Emp Map In Sorted order :
{101=DD, 105=Dipika, 106=Kangna, 102=Kishan, 104=Priyansh, 103=Vimal}

You can also use “sorted(Comparator.reverseOrder())” method to reverse the sorting order. And same thing you can preserve using the LinkedHashMap or TreeMap by passing as a fourth argument.

// 3rd variant - pass
// You can sort in reverse order - Passed LinkedHashMap as 4th argument
Map<Integer, String> empMapWithNewTypeReverseOrder = employees.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toMap(Employee::getEmpId, Employee::getName,
(o1, o2) -> o1, LinkedHashMap::new));

System.out.println("Emp Map With Desired Map Type in Reverse order : " + empMapWithNewTypeReverseOrder);

==================================================
o/p:
Emp Map With Desired Map Type in Reverse order :
{103=Vimal, 104=Priyansh, 102=Kishan, 106=Kangna, 105=Dipika, 101=Darshan}

Check output, which is reversed sorted by the name.

Hope you guys found this helpful, stay tuned.! For any query, you can write to me at dalwadidarshan83@gmail.com. Thank you.!

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Darshan Dalwadi
Darshan Dalwadi

Written by Darshan Dalwadi

Trust your self and you will rock.!

No responses yet

Write a response