简体   繁体   中英

Using streams, how can I map the values in a HashMap?

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 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());

Since Java 9 you can also do:

Entry<String, String> entry = Map.entry("a", "b");

In your Map it would be used like this:

Map<String, String> byNameMap = people.entrySet()
  .stream()
  .map(entry -> Map.entry(entry.getKey(), entry.getValue().getName()))
  .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM