In this article we are going to look at different ways of converting a List of Objects into a Map in Java.
- Using Stream API collect method
- How to handle duplicates keys
- Choose your own Map implementation
Please note that in below examples, in order to get a Stream Object from List,
you can also use List#parallelStream() method,
but use it only if the List implementation supports thread-safety and parallelism (for example CopyOnWriteArrayList).
From Java 8 onwards, you can use Java Stream API to collect elements from a Collection like List.
In order to follow below example, create a class called Person and declare three variables, name as String, age as integer and gender as String.
public class Person {
private String name;
private int age;
private String gender;
public Person(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getGender() {
return gender;
}
@Override
public String toString() {
return "[name : " +name + " , age : "+age + ", gender : "+gender+"]";
}
}
Let's write a program to convert List<Person> into Map<String, Person>
with person name as key and person object as value.
public static void listToMap(){
List<Person> persons = List.of(
new Person("Person A", 29, "Male"),
new Person("Person B", 32, "Female"),
new Person("Person C", 45, "Male"),
new Person("Person D", 39, "Female")
);
Map<String, Person> nameToPersonMap= persons.stream().collect(Collectors.toMap(Person::getName, Function.identity()));
//to print each entry in a line, we are using iterator and print it
nameToPersonMap.entrySet().iterator().forEachRemaining(System.out::println);
}
Explanation of above code:
-
First we called stream() method on List<Person> that returns a Stream<Person> objects.
-
Then on the Stream object, we called collect() method with a Collector
that collects into a Map using Collectors.toMap(key mapper function, value mapper function) which is an utility method that takes two arguments,
first one is the key mapper and second one is the value mapper, in this case key is person name, so we are passing
Person::getName and value is the Person object Function.identity() returns the value itself.
When you run the above program, you will get the below output.
Person A=[name : Person A , age : 29, gender : Male]
Person B=[name : Person B , age : 32, gender : Female]
Person C=[name : Person C , age : 45, gender : Male]
Person D=[name : Person D , age : 39, gender : Female]
Say when are you are converting List of person objects into a Map and the
key you choose have duplicates then it will throw duplicate key exception.
For example, if you run the previous program with gender as key and List<Person> objects as value,
it will throw duplicate key exception, as there are more than one Person object with same gender.
Map<String, List<Person>> nameToPersonMap= persons.stream().collect
(Collectors.toMap(Person::getGender,
person-> {
List<Person> personList = new ArrayList<>();
personList.add(person);
return personList;
}
));
When you run the above code, then it will throw below exception, because Person A and Person C have same keys.
Exception in thread "main" java.lang.IllegalStateException: Duplicate key Male (attempted merging values [[name : Person A , age : 29, gender : Male]] and [[name : Person C , age : 45, gender : Male]])
at java.base/java.util.stream.Collectors.duplicateKeyException(Collectors.java:133)
at java.base/java.util.stream.Collectors.lambda$uniqKeysMapAccumulator$1(Collectors.java:180)
at java.base/java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.base/java.util.AbstractList$RandomAccessSpliterator.forEachRemaining(AbstractList.java:720)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
In this case, you can pass a merge function to handle duplicate keys scenario.
A merge function is a java.util.function.BinaryOperator functional interface which takes oldValue already in the Map and the
newValue that is incoming, and you need to return the resultant of you merge operation,
in this example, we are adding it to existing List<Person> objects.
public static void listToMap_HandleDuplicates(){
List<Person> persons = List.of(
new Person("Person A", 29, "Male"),
new Person("Person B", 32, "Female"),
new Person("Person C", 45, "Male"),
new Person("Person D", 39, "Female")
);
Map<String, List<Person>> nameToPersonMap= persons.stream().collect
(Collectors.toMap(Person::getGender,
person-> {
List<Person> personList = new ArrayList<>();
personList.add(person);
return personList;
}, (oldValue, newValue) ->{
oldValue.addAll(newValue);
return oldValue;
}
));
//to print each entry in a line, we are using iterator and print it
nameToPersonMap.entrySet().iterator().forEachRemaining(System.out::println);
}
When you run the above program, you will get the below output.
Female=[[name : Person B , age : 32, gender : Female], [name : Person D , age : 39, gender : Female]]
Male=[[name : Person A , age : 29, gender : Male], [name : Person C , age : 45, gender : Male]]
In previous examples, we have used Collectors#toMap method which uses HashMap as underlying Map.
You can also use Collectors#toConcurrentMap if you want to use ConcurrentHashMap as underlying map.
Map<String, Person> nameToPersonMap= persons.stream()
.collect(Collectors.toConcurrentMap(Person::getName, Function.identity()));
You can also pass your choice of Map implementation as well, in below example we are passing LinkedHashMap as underlying Map.
public static void listToMap_WithCustomMapImpl() {
List<Person> persons = List.of(
new Person("Person A", 29, "Male"),
new Person("Person B", 32, "Female"),
new Person("Person C", 45, "Male"),
new Person("Person D", 39, "Female")
);
Map<String, Person> nameToPersonMap= persons.stream()
.collect(Collectors.toMap(Person::getName, Function.identity(),
(oldValue, newValue)->oldValue, LinkedHashMap::new ));
//to print each entry in a line, we are using iterator and print it
nameToPersonMap.entrySet().iterator().forEachRemaining(System.out::println);
}
When you run the above program, you will get the below output.
Person A=[name : Person A , age : 29, gender : Male]
Person B=[name : Person B , age : 32, gender : Female]
Person C=[name : Person C , age : 45, gender : Male]
Person D=[name : Person D , age : 39, gender : Female]
Above example complete source code can be found here on Github
Similar Articles
-
Convert a Map into a List of objects using Java Stream API (from Java 8 onwards)
-
Filter and find an element from a List using Java Stream API (from Java 8 onwards)
-
How to remove elements from a List
-
How to remove elements from a List while iterating
-
How to loop through elements of List in different ways