Java8: HashMap to HashMap using Stream / Map-Reduce / Collector
Wiem jak "przekształcić" prostą Javę List
z Y
-> Z
, tj.:
List<String> x;
List<Integer> y = x.stream()
.map(s -> Integer.parseInt(s))
.collect(Collectors.toList());
Teraz chciałbym zrobić w zasadzie to samo z mapą, czyli:
INPUT:
{
"key1" -> "41", // "41" and "42"
"key2" -> "42 // are Strings
}
OUTPUT:
{
"key1" -> 41, // 41 and 42
"key2" -> 42 // are Integers
}
Rozwiązanie nie powinno ograniczać się do String
-> Integer
. Podobnie jak w powyższym przykładzie List
, chciałbym wywołać dowolną metodę (lub konstruktor).
9 answers
Map<String, String> x;
Map<String, Integer> y =
x.entrySet().stream()
.collect(Collectors.toMap(
e -> e.getKey(),
e -> Integer.parseInt(e.getValue())
));
Nie jest tak ładny jak kod listy. Nie można konstruować nowych Map.Entry
s w wywołaniu map()
, więc praca jest mieszana z wywołaniem collect()
.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-06 14:55:33
Oto kilka wariacji na temat odpowiedzi Sotiriosa Delimanolisa , która była całkiem dobra na początek (+1). Rozważmy następujące:
static <X, Y, Z> Map<X, Z> transform(Map<? extends X, ? extends Y> input,
Function<Y, Z> function) {
return input.keySet().stream()
.collect(Collectors.toMap(Function.identity(),
key -> function.apply(input.get(key))));
}
Kilka punktów tutaj. Pierwszym jest użycie symboli wieloznacznych w generykach; to sprawia, że funkcja jest nieco bardziej elastyczna. Symbol wieloznaczny byłby konieczny, jeśli na przykład chcesz, aby Mapa wyjściowa miała klucz, który jest superklasą klucza mapy wejściowej:
Map<String, String> input = new HashMap<String, String>();
input.put("string1", "42");
input.put("string2", "41");
Map<CharSequence, Integer> output = transform(input, Integer::parseInt);
(jest też przykład wartości mapy, ale to naprawdę i przyznam, że posiadanie tzw. wildcard dla Y pomaga tylko w przypadkach skrajnych.)
Druga kwestia jest taka, że zamiast uruchamiać strumień na mapie wejściowej entrySet
, przepuściłem go na keySet
. To sprawia, że kod jest trochę czystszy, moim zdaniem, kosztem konieczności pobierania wartości z mapy zamiast z wpisu mapy. Nawiasem mówiąc, początkowo miałem key -> key
jako pierwszy argument toMap()
i to nie powiodło się z błędem wnioskowania typu z jakiegoś powodu. Zmiana na (X key) -> key
zadziałała, jak did Function.identity()
.
Jeszcze inna odmiana jest następująca:
static <X, Y, Z> Map<X, Z> transform1(Map<? extends X, ? extends Y> input,
Function<Y, Z> function) {
Map<X, Z> result = new HashMap<>();
input.forEach((k, v) -> result.put(k, function.apply(v)));
return result;
}
To używa Map.forEach()
zamiast strumieni. Wydaje mi się, że jest to jeszcze prostsze, ponieważ rezygnuje z kolekcjonerów, które są nieco niezdarne w użyciu z mapami. Powodem jest to, że Map.forEach()
podaje klucz i wartość jako oddzielne parametry, podczas gdy strumień ma tylko jedną wartość - i musisz wybrać, czy użyć klucza, czy wpisu mapy jako tej wartości. Z drugiej strony, brakuje w tym bogatej, strumieniowej dobroci drugiej zbliża się. :-)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:55:10
Takie ogólne rozwiązanie
public static <X, Y, Z> Map<X, Z> transform(Map<X, Y> input,
Function<Y, Z> function) {
return input
.entrySet()
.stream()
.collect(
Collectors.toMap((entry) -> entry.getKey(),
(entry) -> function.apply(entry.getValue())));
}
Przykład
Map<String, String> input = new HashMap<String, String>();
input.put("string1", "42");
input.put("string2", "41");
Map<String, Integer> output = transform(input,
(val) -> Integer.parseInt(val));
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-18 02:23:51
Czy absolutnie musi być w 100% funkcjonalny i płynny? Jeśli nie, to jak o tym, który jest o tak krótki, jak to się robi:
Map<String, Integer> output = new HashMap<>();
input.forEach((k, v) -> output.put(k, Integer.valueOf(v));
(jeśli możesz żyć ze wstydem i winą łączenia strumieni z efektami ubocznymi )
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-28 16:10:28
Moja StreamEx biblioteka rozszerzająca standardowe API strumienia zapewnia EntryStream
klasa, która lepiej nadaje się do przekształcania map:
Map<String, Integer> output = EntryStream.of(input).mapValues(Integer::valueOf).toMap();
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-29 04:52:04
Funkcja guawa Maps.transformValues
jest tym, czego szukasz, i działa dobrze z wyrażeniami lambda:
Maps.transformValues(originalMap, val -> ...)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-05 22:19:13
Jeśli nie masz nic przeciwko używaniu bibliotek stron trzecich, my cyclops-react lib ma rozszerzenia dla wszystkich typów JDK Collection , w tym Map . Możemy po prostu przekształcić mapę bezpośrednio za pomocą operatora 'map' (domyślnie Mapa działa na wartości na mapie).
MapX<String,Integer> y = MapX.fromMap(HashMaps.of("hello","1"))
.map(Integer::parseInt);
Bimap może być używany do przekształcania kluczy i wartości w tym samym czasie
MapX<String,Integer> y = MapX.fromMap(HashMaps.of("hello","1"))
.bimap(this::newKey,Integer::parseInt);
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-23 12:45:22
Alternatywą, która zawsze istnieje dla celów edukacyjnych, jest zbudowanie niestandardowego kolektora za pomocą kolektora.z () choć toMap() JDK jest tutaj zwięzły (+1 Tutaj ).
Map<String,Integer> newMap = givenMap.
entrySet().
stream().collect(Collector.of
( ()-> new HashMap<String,Integer>(),
(mutableMap,entryItem)-> mutableMap.put(entryItem.getKey(),Integer.parseInt(entryItem.getValue())),
(map1,map2)->{ map1.putAll(map2); return map1;}
));
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:56
Deklaratywnym i prostszym rozwiązaniem byłoby:
YourMutableMap.replaceAll ((key, val) - > return_value_of_bi_your_function); Nb. należy pamiętać o modyfikowaniu stanu mapy. Więc to może nie być to, czego chcesz.
Pozdrawiam : http://www.deadcoderising.com/2017-02-14-java-8-declarative-ways-of-modifying-a-map-using-compute-merge-and-replace/
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-10 13:22:57