This is possible using the mergeFunction parameter of Collectors.toMap(keyMapper, valueMapper, mergeFunction):
Map<String, String> phoneBook =
people.stream()
.collect(Collectors.toMap(
Person::getName,
Person::getAddress,
(address1, address2) -> {
System.out.println("duplicate key found!");
return address1;
}
));
mergeFunction is a function that operates on two values associated with the same key. adress1 corresponds to the first address that was encountered when collecting elements and adress2 corresponds to the second address encountered: this lambda just tells to keep the first address and ignores the second.
java - Ignore duplicates when producing map using streams - Stack Overflow
Java-Stream, toMap with duplicate keys - Stack Overflow
Hot Linked Questions - Stack Overflow
How to collect into a Map forming a List in value when duplicate keys in streams, in Java 8 - Stack Overflow
This is possible using the mergeFunction parameter of Collectors.toMap(keyMapper, valueMapper, mergeFunction):
Map<String, String> phoneBook =
people.stream()
.collect(Collectors.toMap(
Person::getName,
Person::getAddress,
(address1, address2) -> {
System.out.println("duplicate key found!");
return address1;
}
));
mergeFunction is a function that operates on two values associated with the same key. adress1 corresponds to the first address that was encountered when collecting elements and adress2 corresponds to the second address encountered: this lambda just tells to keep the first address and ignores the second.
As said in JavaDocs:
If the mapped keys contains duplicates (according to
Object.equals(Object)), anIllegalStateExceptionis thrown when the collection operation is performed. If the mapped keys may have duplicates, usetoMap(Function keyMapper, Function valueMapper, BinaryOperator mergeFunction)instead.
So you should use toMap(Function keyMapper, Function valueMapper, BinaryOperator mergeFunction) instead. Just provide a merge function, that will determine which one of duplicates is put in the map.
For example, if you don't care which one, just call
Map<String, String> phoneBook = people.stream().collect(
Collectors.toMap(Person::getName, Person::getAddress, (a1, a2) -> a1));
Use the other groupingBy overload.
paymentController.findPaymentsByIds(pmtIds)
.stream()
.collect(
groupingBy(Payment::getAbcId, mapping(Payment::getPaymentId, toList());
Problem statement: Converting SimpleImmutableEntry<String, List<String>> -> Map<String, List<String>>.
For Instance you have a SimpleImmutableEntry of this form [A,[1]], [B,[2]], [A, [3]] and you want your map to looks like this: A -> [1,3] , B -> [2].
This can be done with Collectors.toMap but Collectors.toMap works only with unique keys unless you provide a merge function to resolve the collision as said in java docs.
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html#toMap-java.util.function.Function-java.util.function.Function-java.util.function.BinaryOperator-
So the example code looks like this:
.map(returnSimpleImmutableEntries)
.collect(Collectors.toMap(SimpleImmutableEntry::getKey,
SimpleImmutableEntry::getValue,
(oldList, newList) -> { oldList.addAll(newList); return oldList; } ));
returnSimpleImmutableEntries method returns you entries of the form [A,[1]], [B,[2]], [A, [3]] on which you can use your collectors.
That's what groupingBy is for:
Map<String,List<String>> vehicle =
stream.collect(Collectors.groupingBy(e -> e[0],
Collectors.mapping(e -> e[1],
Collectors.toList())));
Output map:
{CAR=[Audi], BIKE=[Harley Davidson, Pulsar]}
You can use groupingBy
getMapStream()
.map(item -> Arrays.asList(item))
.collect(Collectors.groupingBy(l->l.get(0),
Collectors.mapping(l1->l1.get(1),Collectors.toList())));
or use toMap() with merge function.
Map<String,List<String>> vehicle = getMapStream()
.collect(Collectors.toMap(item->item[0],
item->new ArrayList<>(Arrays.asList(item[1])),
(l1,l2)->{l1.addAll(l2);return l1;}));
As mentioned in the comment, maps do not store duplicated keys so you have to use Map<String, List<String>> instead Map<String, String>.
To sum up, you just have to use Collectors.toMap method with mergeFunction parameter where you can handle duplicated keys. This operation will be called every time when same keys appears. In this case, we simply merge two lists to one. Have a look at below code (compiled with JDK 11), I believe it does exactly what you need and prints an expected result (using List<String> of course).
import java.util.*;
import java.util.stream.Collectors;
public class ListToMapWithDuplicatedKeysSample {
public static void main(String[] args) {
List<Address> addresses = List.of(
new Address("City1", "country1", "state1"),
new Address("City1", "country1", "state2"),
new Address("City1", "country1", "state1"),
new Address("City3", "country3", "state3")
);
Map<String, List<String>> result = addresses.stream()
.collect(
Collectors.toMap(
address -> address.getCity() + address.getCountry(),
address -> Collections.singletonList(address.getState()),
ListToMapWithDuplicatedKeysSample::mergeEntriesWithDuplicatedKeys
)
);
System.out.println(result);
}
private static List<String> mergeEntriesWithDuplicatedKeys(List<String> existingResults, List<String> newResults) {
List<String> mergedResults = new ArrayList<>();
mergedResults.addAll(existingResults);
mergedResults.addAll(newResults);
return mergedResults;
}
private static class Address {
private final String city;
private final String country;
private final String state;
public Address(String city, String country, String state) {
this.city = city;
this.country = country;
this.state = state;
}
String getState() {
return state;
}
String getCountry() {
return country;
}
String getCity() {
return city;
}
}
}
You can’t use Collectors.toMap, because it requires unique keys. What you want instead is groupingBy, which returns collected values for each key:
class Location {
public final String city;
public final String country;
Location(Address address) {
this.city = address.getCity();
this.country = address.getCountry();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Location) {
Location other = (Location) obj;
return Objects.equals(this.city, other.city) &&
Objects.equals(this.country, other.country);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(city, country);
}
}
Map<Location, Set<String>> statesByLocation = addresses.stream().collect(
Collectors.groupingBy(Location::new,
Collectors.mapping(Address::getState, Collectors.toSet())));
You could employ various hacks for combining city and country (like a list of two Strings), but you really should just make a key class as shown above. The code will be far easier to work with, especially if you (or another developer) should have a reason to come back to it in six months.
As for the Collector methods:
Collectors.groupingBy(Location::newis equivalent toaddress -> new Location(address)and creates each key in the Map.Collectors.mapping(Address::getState, Collectors.toSet())means that each Address which corresponds to a particular key will have getState() invoked on it, and the resulting strings will be aggregated in a Set to form the Map value.
Personally, I think I would opt for a simple for-loop instead of the hard-to-read Stream approach:
Map<Location, Set<String>> statesByLocation = new HashMap<>();
for (Address address : addresses) {
statesByLocation.computeIfAbsent(new Location(address),
k -> new HashSet<String>()).add(address.getState());
}