Java8: HashMap для HashMap с использованием Stream / Map-Reduce / Collector
Я знаю, как" преобразовать " простой Java List С Y ->Z, например:
List<String> x;
List<Integer> y = x.stream()
.map(s -> Integer.parseInt(s))
.collect(Collectors.toList());
теперь я хотел бы сделать в основном то же самое с картой, т. е.:
INPUT:
{
"key1" -> "41", // "41" and "42"
"key2" -> "42 // are Strings
}
OUTPUT:
{
"key1" -> 41, // 41 and 42
"key2" -> 42 // are Integers
}
решение не должно ограничиваться String ->Integer. Так же, как и в List выше примере, я хотел бы вызвать любой метод (или конструктор).
9 ответов:
Map<String, String> x; Map<String, Integer> y = x.entrySet().stream() .collect(Collectors.toMap( e -> e.getKey(), e -> Integer.parseInt(e.getValue()) ));это не совсем так хорошо, как код списка. Вы не можете построить новый
Map.EntryСmap()вызов так что работа смешивается вcollect()звонок.
вот некоторые вариации на ответ Сотириоса Делиманолиса, что было довольно хорошо для начала (+1). Рассмотрим следующее:
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)))); }несколько моментов здесь. Во-первых, это использование подстановочных знаков в обобщениях; это делает функцию несколько более гибкой. Подстановочный знак будет необходим, если, например, вы хотите, чтобы выходная карта имела ключ, который является суперклассом ключа входной карты:
Map<String, String> input = new HashMap<String, String>(); input.put("string1", "42"); input.put("string2", "41"); Map<CharSequence, Integer> output = transform(input, Integer::parseInt);(есть также пример для карты значения, но это действительно надуманно, и я признаю, что наличие ограниченного подстановочного знака для Y помогает только в крайних случаях.)
второй момент заключается в том, что вместо запуска потока над входной картой
entrySet, Я пробежал его поkeySet. Это делает код немного чище, я думаю, за счет необходимости извлекать значения из карты, а не из записи карты. Кстати, у меня изначально былоkey -> keyкак первый аргумент кtoMap()и это не удалось с ошибкой вывода типа для некоторая причина. Изменение его на(X key) -> keyработал, как иFunction.identity().еще один вариант выглядит следующим образом:
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; }использует
Map.forEach()вместо потоков. Это еще проще, я думаю, потому что он обходится без коллекционеров, которые несколько неуклюжи в использовании с картами. Причина в том, чтоMap.forEach()дает ключ и значение как отдельные параметры, тогда как поток имеет только одно значение - и вы должны выбрать, Использовать ли ключ или запись карты в качестве этого значение. С другой стороны, в этом нет богатой, струящейся доброты других подходов. : -)
общее решение, как так
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()))); }пример
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));
это абсолютно должно быть 100% функциональным и свободно? Если нет, как насчет этого, что примерно так же коротко, как и получается:
Map<String, Integer> output = new HashMap<>(); input.forEach((k, v) -> output.put(k, Integer.valueOf(v));(если вы можете жить с позором и виной объединения потоков с побочными эффектами)
мой StreamEx библиотека, которая улучшает стандартный поток API обеспечивает
EntryStreamкласс, который лучше подходит для преобразования карты:Map<String, Integer> output = EntryStream.of(input).mapValues(Integer::valueOf).toMap();
функция гуавы
Maps.transformValuesэто то, что вы ищете, и это хорошо работает с лямбда-выражениями:Maps.transformValues(originalMap, val -> ...)
Если вы не возражаете использовать сторонние библиотеки, мой Циклоп-реагировать lib имеет расширения для всех коллекция JDK типы, в том числе карта. Мы можем просто преобразовать карту непосредственно с помощью оператора ' map ' (по умолчанию карта действует на значения в карте).
MapX<String,Integer> y = MapX.fromMap(HashMaps.of("hello","1")) .map(Integer::parseInt);bimap может использоваться для преобразования ключей и значений одновременно
MapX<String,Integer> y = MapX.fromMap(HashMaps.of("hello","1")) .bimap(this::newKey,Integer::parseInt);
альтернативой, которая всегда существует для целей обучения, является создание пользовательского коллектора через Collector.хотя tomap () JDK collector здесь лаконичен (+1 здесь).
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;} ));
декларативным и более простым решением было бы:
yourMutableMap.replaceAll ((key, val) - > return_value_of_bi_your_function); Нотабене. помните, что вы изменяете свое состояние карты. Так что это может быть не то, что вы хотите.
Comments