Given a Map<String, Person>
where Person has a String getName()
(etc) method on it, how can I turn the Map<String, Person>
into a Map<String, String>
where the String
is obtained from calling Person::getName()
?
Pre-Java 8 I'd use
Map<String, String> byNameMap = new HashMap<>();
for (Map.Entry<String, Person> person : people.entrySet()) {
byNameMap.put(person.getKey(), person.getValue().getName());
}
but I'd like to do it using streams and lambdas.
I can't see how to do this in a functional style: Map/HashMap don't implement Stream
.
people.entrySet()
returns a Set<Entry<String, Person>>
which I can stream over, but how can I add a new Entry<String, String>
to the destination map?
With Java 8 you can do:
Map<String, String> byNameMap = new HashMap<>();
people.forEach((k, v) -> byNameMap.put(k, v.getName());
Though you'd be better off using Guava's Maps.transformValues, which wraps the original Map
and does the conversion when you do the get
, meaning you only pay the conversion cost when you actually consume the value.
Using Guava would look like this:
Map<String, String> byNameMap = Maps.transformValues(people, Person::getName);
EDIT:
Following @Eelco's comment (and for completeness), the conversion to a map is better down with Collectors.toMap like this:
Map<String, String> byNameMap = people.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, (entry) -> entry.getValue().getName());
One way is to use a toMap
collector:
import static java.util.stream.Collectors.toMap;
Map<String, String> byNameMap = people.entrySet().stream()
.collect(toMap(Entry::getKey,
e -> e.getValue().getName()));
Using a bit of generic code that I've sadly not found in the libraries I had at hand
public static <K, V1, V2> Map<K, V2> remap(Map<K, V1> map,
Function<? super V1, ? extends V2> function) {
return map.entrySet()
.stream() // or parallel
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> function.apply(e.getValue())
));
}
This becomes essentially the same as Guavas Maps.transformValues
minus the downsides mentioned by others.
Map<String, Person> persons = ...;
Map<String, String> byNameMap = remap(persons, Person::getName);
And in case you need the key as well as the value in your remapping function, this second version makes that possible
public static <K, V1, V2> Map<K, V2> remap(Map<K, V1> map,
BiFunction<? super K, ? super V1, ? extends V2> function) {
return map.entrySet()
.stream() // or parallel
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> function.apply(e.getKey(), e.getValue())
));
}
It can be used for example like
Map<String, String> byNameMap = remap(persons, (key, val) -> key + ":" + val.getName());