handle duplicate key in Collectors.toMap() function
Solution 1
if you want to access the whole person object in the merge function then pass Function.identity()
for the valueMapper
:
Map<String, Person> myMap =
persons.stream()
.collect(toMap(p -> p.getName(),
Function.identity(), // or p -> p
(p1, p2) -> { /* do logic */ }));
But as you can see the resulting map values are Person
objects, if you still want a Map<String, String>
as a result and still access the whole Person
object in the mergeFunction
then you can do the following:
persons.stream()
.collect(toMap(p -> p.getName(), Function.identity(),(p1, p2) -> { /* do logic */ }))
.entrySet()
.stream()
.collect(toMap(Map.Entry::getKey, p -> p.getValue().getAddress()));
Solution 2
@Aomine: solution looks good and works for me too. Just wanted to confirm that with this it iterates twice right ?? Cause with simple solution like below it iterates only once but achieve what is required.
Map<String,String> myMap= new HashMap<>();
persons.stream().foreach(item-> {
if(myMap.containsKey(item.getName()))
{/*do something*/}
else
myMap.put(item.getName(),item.getAddress());
});
Solution 3
I believe the forEach
approach along with Map.merge
would be much simpler and appropriate for the current use case :
Map<String, String> myMap = new HashMap<>();
persons.forEach(person -> myMap.merge(person.getName(), person.getAddress(), (adrs1, adrs2) -> {
System.out.println("duplicate " + person.getName() + " is found!");
return adrs1;
}));
Note: Map.merge
also uses BiFunction
(parent of BinaryOperator
as used in toMap
), hence you could correlate the merge function here to your existing desired functionality easily.
Solution 4
Here is another possibility using peek
import java.util.*;
import java.util.stream.*;
class App {
public static void main(String[] args) {
List<Person> persons = Arrays.asList(new Person("foo", "bar"), new Person("baz", "qux"), new Person("foo", "zuz"));
Set<String> names = new HashSet<>();
Map<String, String> nameAddress = persons.stream().peek(p -> {
if (names.contains(p.getName())) {
System.out.println("Duplicate key being skipped: " + p);
} else {
names.add(p.getName());
}
}).collect(Collectors.toMap(person -> person.getName(), person -> person.getAddress(), (addr1, addr2) -> addr1));
}
}
class Person {
String name;
String address;
public Person(String name, String address) {
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public String getAddress() {
return address;
}
@Override
public String toString() {
return name + " " + address;
}
}
Output for me will be as follows for the snippet above:
Duplicate key being skipped: foo zuz
Bali
Updated on June 11, 2022Comments
-
Bali about 2 years
I am creating a map which its (key,value) will be (name, address) in my Person object:
Map<String, String> myMap = persons.stream.collect(Collector.toMap(person.getName(), person.getAddress(), (address1, address2) -> address1));
In the duplicate key situation, I would like to skip to add the second address to the map and would like to log the name also. Skipping the duplicate address I can do already using mergeFunction, but in oder to log the name I need in this mergeFunction the person object, something like:
(address1, address2) -> { System.out.println("duplicate "+person.name() + " is found!"); return address1; }
I am getting stuck by passing person object to this merge function.
-
Bali over 5 yearsis it possible that I keep the Map<String, String> but I would like to access the person.name() in my logic method?
-
Bali over 5 yearshi Aomine, your second solution sounds good, but I got compile error at the last line : p -> p.getValue().getAddress(). Do you have any idea?
-
Bali over 5 years@Aomine sorry my typo mistake. It works now, thank you very much for your help :)
-
DodgyCodeException over 5 yearsYes, Aomine's answer does indeed iterate twice. You can also use
Map.putIfAbsent
to simplify your code. -
ygbgames over 5 yearsActually i can use it but in the questions. It was asked for to use the name if it is already present so didnt use Map.putIfAbsent.
-
DodgyCodeException over 5 yearsYou can still call Map.putIfAbsent. You supply the name as the key and the address as the value. So if putIfAbsent returns non-null, it means this is a duplicate key, and now you already have the name (which you passed to putIfAbsent as an argument) which you can use in your System.out.println/etc. So it's simply this:
if (myMap.putIfAbsent(item.getName(), item.getAdress()) != null) { System.out.println(item.getName() + " already exists."); }
. -
Sham Fiorin over 4 yearsLove the solution, i', using: