使用Stream / Map-Reduce / Collector的Java8:HashMap <X,Y>到HashMap <X,Z>

我知道如何从Y – > Z “转换”一个简单的Java List ,即:

 List<String> x; List<Integer> y = x.stream() .map(s -> Integer.parseInt(s)) .collect(Collectors.toList()); 

现在我想用一个Map来做基本相同的事情,即:

 INPUT: { "key1" -> "41", // "41" and "42" "key2" -> "42 // are Strings } OUTPUT: { "key1" -> 41, // 41 and 42 "key2" -> 42 // are Integers } 

解决scheme不应该限于String – > Integer 。 就像在上面的List例子中,我想调用任何方法(或构造函数)。

 Map<String, String> x; Map<String, Integer> y = x.entrySet().stream() .collect(Collectors.toMap( e -> e.getKey(), e -> Integer.parseInt(e.getValue()) )); 

这不像列表代码那么好。 您不能在map()调用中构造新的Map.Entry ,因此工作被混合到collect()调用中。

这里有一些关于Sotirios Delimanolis的答案 ,这个答案 (+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)))); } 

这里有一对夫妇。 首先是在generics中使用通配符; 这使得这个function更加灵活。 例如,如果希望输出映射具有input映射关键字的超类,则必须使用通配符:

 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的有界通配符只对边缘情况有帮助。

第二点是,不是在input映射的entrySet上运行stream,而是通过keySet运行它。 这使代码更清洁,我认为,代价是必须从地图中提取值,而不是从地图项中提取值。 顺便说一句,我最初有key -> key作为toMap()的第一个参数,并由于某种原因,这种types推断错误失败。 将其更改为(X key) -> keyFunction.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()而不是stream。 我认为这更简单,因为它省去了收集器,这些收集器在地图上使用起来有些笨拙。 原因是Map.forEach()将键和值作为单独的参数给出,而stream只有一个值 – 您必须select是使用键还是映射条目作为该值。 在负面的方面,这缺乏其他方法丰富,stream畅的善意。 🙂

像这样的通用解决scheme

 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%function和stream利吗? 如果没有,那么这个怎么样呢?

 Map<String, Integer> output = new HashMap<>(); input.forEach((k, v) -> output.put(k, Integer.valueOf(v)); 

( 如果你能忍受stream氓和副作用的耻辱和内疚 )

我的增强标准streamAPI的StreamEx库提供了一个适合转换映射的EntryStream类:

 Map<String, Integer> output = EntryStream.of(input).mapValues(Integer::valueOf).toMap(); 

一个总是存在的学习目的的替代方法是通过Collector.of()来构build自定义收集器,尽pipetoMap()JDK收集器简洁(这里是+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;} )); 

如果您不介意使用第三方库,我的独眼巨人反应 lib具有扩展所有JDK集合types,包括地图 。 我们可以直接使用“地图”运算符来转换地图(默认地图会影响地图中的值)。

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

番石榴的函数Maps.transformValues是你正在寻找的,它与lambdaexpression式很好地工作:

 Maps.transformValues(originalMap, val -> ...)