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

我知道如何“转化”;一个简单的Java List from Y ->Z,即:

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
}

解决方案不应该局限于String ->Integer。就像上面的List例子一样,我想调用任何方法(或构造函数)。

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

它不像列表代码那么好。你不能在map()调用中构造新的__abc0,所以工作被混合到collect()调用中。

像这样的一般解

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

下面是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))));
}

这里有几点。首先是在泛型中使用通配符;这使得函数在某种程度上更加灵活。通配符是必要的,例如,如果你想要输出映射有一个键是输入映射的键的超类:

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上运行流。我认为这使得代码更简洁,代价是必须从map条目中获取值,而不是从map条目中获取值。顺便说一句,我最初将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()给出了键和值作为单独的参数,而流只有一个值——你必须选择是使用键还是使用映射项作为该值。缺点是,这种方法缺乏其他方法丰富、流畅的优点。: -)

它一定要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();

如果你不介意使用第三方库,我的cyclops-react库有所有JDK集合类型的扩展,包括地图。我们可以直接使用'map'操作符转换map(默认情况下map作用于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 .of()构建自定义收集器,尽管toMap() 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;}
));

Guava的函数Maps.transformValues就是你要找的,它可以很好地与lambda表达式一起工作:

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

声明式的更简单的解决方案是:

地图。replaceAll((key, val) ->getUpdatedListFor(关键,val));

< p > yourMutableMap。((key, val) return_value_of_bi_your_function); Nb。注意你在修改你的地图状态。所以这可能不是你想要的

为: http://www.deadcoderising.com/2017-02-14-java-8-declarative-ways-of-modifying-a-map-using-compute-merge-and-replace/ < / p >