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

Ben*_*n M 188 java mapreduce java-8 java-stream collectors

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

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

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

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

OUTPUT:
{
  "key1" -> 41,      // 41 and 42
  "key2" -> 42       // are Integers
}
Run Code Online (Sandbox Code Playgroud)

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

Joh*_*ica 336

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

它不如列表代码那么好.您无法Map.Entrymap()呼叫中构建新的s,因此工作会混合到collect()呼叫中.

  • 你可以用`Map.Entry :: getKey`替换`e - > e.getKey()`.但这是品味/编程风格的问题. (56认同)
  • 实际上这是一个性能问题,你的建议略微优于lambda'风格' (5认同)

Stu*_*rks 34

以下是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))));
}
Run Code Online (Sandbox Code Playgroud)

这里有几点.首先是在泛型中使用通配符; 这使得功能更加灵活.例如,如果您希望输出映射具有输入映射键的超类的键,则需要使用通配符:

Map<String, String> input = new HashMap<String, String>();
input.put("string1", "42");
input.put("string2", "41");
Map<CharSequence, Integer> output = transform(input, Integer::parseInt);
Run Code Online (Sandbox Code Playgroud)

(地图的值也有一个例子,但它确实是人为的,我承认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;
}
Run Code Online (Sandbox Code Playgroud)

这使用Map.forEach()而不是流.我认为这更简单,因为它省去了收藏家,这些收藏家与地图一起使用有些笨拙.原因是Map.forEach()将键和值作为单独的参数,而流只有一个值 - 您必须选择是使用键还是映射条目作为该值.从缺点来看,这缺乏其他方法的丰富,流动的优点.:-)

  • `Function.identity()`可能看起来很酷但是因为第一个解决方案需要对每个条目进行map/hash查找,而所有其他解决方案都没有,我不推荐它. (11认同)

Sot*_*lis 12

像这样的通用解决方案

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())));
}
Run Code Online (Sandbox Code Playgroud)

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));
Run Code Online (Sandbox Code Playgroud)


Ale*_*uss 12

Guava的功能Maps.transformValues是你正在寻找的,它与lambda表达式很好地配合:

Maps.transformValues(originalMap, val -> ...)
Run Code Online (Sandbox Code Playgroud)


Luk*_*der 10

它绝对必须100%功能和流畅吗?如果没有,那么这个怎么样,就像它得到的一样短:

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

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


Tag*_*eev 5

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

Map<String, Integer> output = EntryStream.of(input).mapValues(Integer::valueOf).toMap();
Run Code Online (Sandbox Code Playgroud)