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

问题

我知道如何"转换"一个简单的JavaList来自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
}

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


#1 热门回答(237 赞)

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

它不如列表代码那么好。你不能在amap()call中构建newMap.Entrys,所以工作混合到了collect()call。


#2 热门回答(26 赞)

以下是Sotirios Delimanolis' answer的一些变体,这一点非常好(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);

( Map 的值也有一个例子,但它确实是人为的,我承认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 有些笨拙。原因是Map.forEach()将键和值作为单独的参数,而流只有一个值 - 你必须选择是使用键还是映射条目作为该值。从负面来看,这缺乏其他方法丰富,流畅的优点。 :-)


#3 热门回答(11 赞)

像这样的通用解决方案

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