ordenado - mapping en java
Usando flujos, ¿cómo puedo mapear los valores en un HashMap? (3)
Dado un Map<String, Person>
donde la Persona tiene un método String getName()
(etc.), ¿cómo puedo convertir el Map<String, Person>
en un Map<String, String>
donde la String
se obtiene de la Person::getName()
llama? Person::getName()
?
Pre-Java 8 yo usaría
Map<String, String> byNameMap = new HashMap<>();
for (Map.Entry<String, Person> person : people.entrySet()) {
byNameMap.put(person.getKey(), person.getValue().getName());
}
pero me gustaría hacerlo usando arroyos y lambdas.
No puedo ver cómo hacer esto en un estilo funcional: Map / HashMap no implementa Stream
.
people.entrySet()
devuelve un Set<Entry<String, Person>>
que puedo transmitir, pero ¿cómo puedo agregar una nueva Entry<String, String>
al mapa de destino?
Con Java 8 puedes hacer:
Map<String, String> byNameMap = new HashMap<>();
people.forEach((k, v) -> byNameMap.put(k, v.getName());
Aunque sería mejor utilizar los Maps.transformValues de Guava, que envuelven el Map
original y realiza la conversión cuando se get
, lo que significa que solo paga el costo de la conversión cuando realmente consume el valor.
El uso de guayaba se vería así:
Map<String, String> byNameMap = Maps.transformValues(people, Person::getName);
EDITAR:
Siguiendo el comentario de @ Eelco (y para completar), la conversión a un mapa es mejor con Collectors.toMap así:
Map<String, String> byNameMap = people.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, (entry) -> entry.getValue().getName());
Una forma es usar un colector toMap
:
import static java.util.stream.Collectors.toMap;
Map<String, String> byNameMap = people.entrySet().stream()
.collect(toMap(Entry::getKey,
e -> e.getValue().getName()));
Usando un poco de código genérico que lamentablemente no he encontrado en las bibliotecas que tenía a mano
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())
));
}
Esto se vuelve esencialmente igual a Maps.transformValues
menos las desventajas mencionadas por otros.
Map<String, Person> persons = ...;
Map<String, String> byNameMap = remap(persons, Person::getName);
Y en caso de que necesite la clave así como el valor en su función de reasignación, esta segunda versión lo hace posible
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())
));
}
Se puede utilizar por ejemplo como
Map<String, String> byNameMap = remap(persons, (key, val) -> key + ":" + val.getName());