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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25903137/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 01:26:16  来源:igfitidea点击:

Java8: HashMap<X, Y> to HashMap<X, Z> using Stream / Map-Reduce / Collector

javamapreducejava-8java-streamcollectors

提问by Benjamin M

I know how to "transform" a simple Java Listfrom Y-> Z, i.e.:

我知道如何ListY-> 中“转换”一个简单的 Java Z,即:

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

Now I'd like to do basically the same with a Map, i.e.:

现在我想对 Map 做基本相同的事情,即:

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

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

The solution should not be limited to String-> Integer. Just like in the Listexample above, I'd like to call any method (or constructor).

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

采纳答案by John Kugelman

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

It's not quite as nice as the list code. You can't construct new Map.Entrys in a map()call so the work is mixed into the collect()call.

它不像列表代码那么好。您不能Map.Entrymap()通话中构造 new ,因此工作会混入collect()通话中。

回答by Sotirios Delimanolis

A generic solution like so

像这样的通用解决方案

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

Example

例子

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

回答by Stuart Marks

Here are some variations on Sotirios Delimanolis' answer, which was pretty good to begin with (+1). Consider the following:

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

A couple points here. First is the use of wildcards in the generics; this makes the function somewhat more flexible. A wildcard would be necessary if, for example, you wanted the output map to have a key that's a superclass of the input map's 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);

(There is also an example for the map's values, but it's really contrived, and I admit that having the bounded wildcard for Y only helps in edge cases.)

(还有一个关于地图值的示例,但它确实是人为设计的,我承认为 Y 设置有界通配符仅在边缘情况下有帮助。)

A second point is that instead of running the stream over the input map's entrySet, I ran it over the keySet. This makes the code a little cleaner, I think, at the cost of having to fetch values out of the map instead of from the map entry. Incidentally, I initially had key -> keyas the first argument to toMap()and this failed with a type inference error for some reason. Changing it to (X key) -> keyworked, as did Function.identity().

第二点是entrySet,我没有在输入地图上运行流,而是在keySet. 我认为这使代码更简洁一些,代价是必须从地图而不是从地图条目中获取值。顺便说一句,我最初key -> key将第一个参数作为 totoMap()并且由于某种原因导致类型推断错误失败。将其更改为(X key) -> key有效,就像Function.identity().

Still another variation is as follows:

还有一个变化如下:

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

This uses Map.forEach()instead of streams. This is even simpler, I think, because it dispenses with the collectors, which are somewhat clumsy to use with maps. The reason is that Map.forEach()gives the key and value as separate parameters, whereas the stream has only one value -- and you have to choose whether to use the key or the map entry as that value. On the minus side, this lacks the rich, streamy goodness of the other approaches. :-)

这使用Map.forEach()代替流。我认为这甚至更简单,因为它省去了收集器,这些收集器与地图一起使用有些笨拙。原因是Map.forEach()将键和值作为单独的参数给出,而流只有一个值——您必须选择是使用键还是映射条目作为该值。不利的一面是,这缺乏其他方法的丰富、流畅的优点。:-)

回答by Lukas Eder

Does it absolutely have to be 100% functional and fluent? If not, how about this, which is about as short as it gets:

它是否绝对必须 100% 实用且流畅?如果没有,这个怎么样,它尽可能短:

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

(if you can live with the shame and guilt of combining streams with side-effects)

如果你能忍受将流与副作用结合起来的羞耻感和内疚感

回答by Tagir Valeev

My StreamExlibrary which enhances standard stream API provides an EntryStreamclass which suits better for transforming maps:

我的StreamEx库增强了标准流 API,它提供了一个EntryStream更适合转换地图的类:

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

回答by John McClean

If you don't mind using 3rd party libraries, my cyclops-reactlib has extensions for all JDK Collectiontypes, including Map. We can just transform the map directly using the 'map' operator (by default map acts on the values in the map).

如果您不介意使用 3rd 方库,我的cyclops-react库具有适用于所有JDK 集合类型的扩展,包括Map。我们可以直接使用“map”操作符直接转换地图(默认情况下,地图作用于地图中的值)。

   MapX<String,Integer> y = MapX.fromMap(HashMaps.of("hello","1"))
                                .map(Integer::parseInt);

bimap can be used to transform the keys and values at the same time

bimap 可用于同时转换键和值

  MapX<String,Integer> y = MapX.fromMap(HashMaps.of("hello","1"))
                               .bimap(this::newKey,Integer::parseInt);

回答by Ira

An alternative that always exists for learning purpose is to build your custom collector through Collector.of() though toMap() JDK collector here is succinct (+1 here) .

总是存在学习目的的替代方法是通过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;}
               ));

回答by Alex Krauss

Guava's function Maps.transformValuesis what you are looking for, and it works nicely with lambda expressions:

Guava 的函数Maps.transformValues正是您要寻找的,它可以很好地与 lambda 表达式配合使用:

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

回答by Breton F.

The declarative and simpler solution would be :

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

yourMutableMap.replaceAll((key, val) -> return_value_of_bi_your_function); Nb. be aware your modifying your map state. So this may not be what you want.

yourMutableMap.replaceAll((key, val) -> return_value_of_bi_your_function); 铌。请注意您正在修改地图状态。所以这可能不是你想要的。

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

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