In List to Map conversion, we will see how to maintain unique keys. Sometimes when you use a key other than the primary key it is possible that there might be duplicate records. In those cases, you will get a Duplicate key IllegalStateException.
In toMap method we can pass key and value pairs as arguments. When using the key you have to choose the right POJO getter method or combination of two getter methods.
Already in the 5th element of the list, we have a Subaru as a Sedan type. Again later the type is classified as Sedan Sports, but the brand and model have the same value and are considered as duplicates when used as a key.
Here we see two ways to avoid Duplicate key exception.
ListToMapDuplicateKey class
package com.digitizedpost.vehicle;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class ListToMapDuplicateKey {
private List<Car> getCarList() {
List<Car> cars = new ArrayList<>();
cars.add(new Car(1, "Coupe", "Audi", "Audi A5"));
cars.add(new Car(2, "Sedan", "Honda", "Honda Accord"));
cars.add(new Car(3, "Suv", "Land Rover", "Range Rover"));
cars.add(new Car(4, "Sports", "Porsche","Porsche 911"));
cars.add(new Car(5, "Sedan", "Subaru", "Subaru WRX"));
cars.add(new Car(6, "Hatchback", "Volkswagen", "Volkswagen Golf"));
cars.add(new Car(7, "Sedan Sports", "Subaru", "Subaru WRX"));
return cars;
}
private Map<String, String> convertListToMap(List<Car> carList) {
Map<String, String> map = carList.stream().
collect(Collectors.toMap(Car::getModel, Car::getBrand));
return map;
}
public static void main(String[] args) {
List<Car> carList = new ListToMapDuplicateKey().getCarList();
Map<String, String> carMap = new ListToMapDuplicateKey().convertListToMap(carList);
System.out.println("map : " + carMap);
}
}
On running the above program it throws java.lang.IllegalStateException exception.
Exception in thread "main" java.lang.IllegalStateException: Duplicate key Subaru WRX (attempted merging values Subaru and Subaru)
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.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1625)
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)
at com.digitizedpost.vehicle.ListToMapDuplicateKey.convertListToMap(ListToMapDuplicateKey.java:24)
at com.digitizedpost.vehicle.ListToMapDuplicateKey.main(ListToMapUniqueKey.java:30)
Using Merge Function
To deal with the duplicate key introduce the mergeFunction as another argument. mergeFunction will resolve the duplicate key issue. mergeFunction is defined as a binary operator used to resolve the collisions between values associated with the same key.
We are passing values (oldValue and newValue) of the same key as an input to the mergeFunction.
From the merge function based on your requirement, you can choose oldValue or newValue. I am going to choose the new value.
Collectors.toMap(Car::getModel, Car::getType,
(existingValue, newValue) -> newValue)
map : {Audi A5=Coupe, Porsche 911=Sports, Volkswagen Golf=Hatchback, Honda Accord=Sedan, Range Rover=Suv, Subaru WRX=Sedan Sports}
Combining more than one properties getter method
Here we are combining two POJO properties getter methods as key. Its a kind of composite key where two or more properties getter method is combined together to get a unique key.
Instead of method reference, you have to use lambda to combine key.
Collectors.toMap(car -> car.getId() + "-" + car.getModel(),
Car::getBrand)
A dash is added between the keys for identification.
map : {5-Subaru WRX=Subaru, 1-Audi A5=Audi, 6-Volkswagen Golf=Volkswagen, 4-Porsche 911=Porsche, 7-Subaru WRX=Subaru, 3-Range Rover=Land Rover, 2-Honda Accord=Honda}
If you still attempt to use method reference you will get the compiler exception like The target type of this expression must be a functional interface.
//Don't do like this
Collectors.toMap(Car::getId + "-" + Car::getModel,
Car::getBrand)
Exception in thread "main" java.lang.Error: Unresolved compilation problems:
The target type of this expression must be a functional interface
The target type of this expression must be a functional interface
at com.digitizedpost.vehicle.ListToMapUniqueKey.convertListToMap(ListToMapUniqueKey.java:31)
at com.digitizedpost.vehicle.ListToMapUniqueKey.main(ListToMapUniqueKey.java:38)
toMap single property getter method as the key
Also if there is a unique primary key from the database is set in the POJO, it can be used as a unique key,
private Map<Integer, String> convertListToMapWithSingleKey(List<Car> carList) {
Map<Integer, String> map = carList.stream().
collect(Collectors.toMap(Car::getId, Car::getBrand));
return map;
}
getId return type is int. So if you use int instead of Integer in generics the IDE will show a syntax error,
Syntax error, insert "Dimensions" to complete TypeArgument
Final Code
ListToMapUniqueKey class
package com.digitizedpost.vehicle;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class ListToMapUniqueKey {
private List<Car> getCarList() {
List<Car> cars = new ArrayList<>();
cars.add(new Car(1, "Coupe", "Audi", "Audi A5"));
cars.add(new Car(2, "Sedan", "Honda", "Honda Accord"));
cars.add(new Car(3, "Suv", "Land Rover", "Range Rover"));
cars.add(new Car(4, "Sports", "Porsche","Porsche 911"));
cars.add(new Car(5, "Sedan", "Subaru", "Subaru WRX"));
cars.add(new Car(6, "Hatchback", "Volkswagen", "Volkswagen Golf"));
cars.add(new Car(7, "Sedan Sports", "Subaru", "Subaru WRX"));
return cars;
}
private Map<String, String> convertListToMapWithMergeFunction(List<Car> carList) {
Map<String, String> map = carList.stream().
collect(Collectors.toMap(Car::getModel, Car::getType,
(existingValue, newValue) -> newValue));
return map;
}
private Map<String, String> convertListToMapWithTwoKeys(List<Car> carList) {
Map<String, String> map = carList.stream().
collect(Collectors.toMap(car -> car.getId() + "-" + car.getModel(),
Car::getBrand));
return map;
}
private Map<Integer, String> convertListToMapWithSingleKey(List<Car> carList) {
Map<Integer, String> map = carList.stream().
collect(Collectors.toMap(Car::getId, Car::getBrand));
return map;
}
public static void main(String[] args) {
List<Car> carList = new ListToMapUniqueKey().getCarList();
Map<String, String> carMap1 = new ListToMapUniqueKey().convertListToMapWithMergeFunction(carList);
System.out.println("map1 : " + carMap1);
Map<String, String> carMap2 = new ListToMapUniqueKey().convertListToMapWithTwoKeys(carList);
System.out.println("map2 : " + carMap2);
Map<Integer, String> carMap3 = new ListToMapUniqueKey().convertListToMapWithSingleKey(carList);
System.out.println("map3 : " + carMap3);
}
}