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?
Yes, you can map each entry to another temporary entry that will hold the key and the parsed integer value. Then you can filter each entry based on their value. Map<String, Integer> output = input. entrySet() .
The map() function is a method in the Stream class that represents a functional programming concept. In simple words, the map() is used to transform one object into other by applying a function. That's why the Stream. map(Function mapper) takes a function as an argument.
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 done 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());
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With