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

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

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

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

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

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

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


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

它不如列表代码那么好。您无法在调用中构造新的 s,因此工作将混合到调用中。Map.Entrymap()collect()


答案 2

以下是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的有界通配符仅在边缘情况下有帮助。

第二点是,我没有在输入映射上运行流,而是在 .我认为,这使得代码更清晰一些,代价是必须从映射中获取值,而不是从映射条目中获取值。顺便说一句,我最初作为第一个参数,由于某种原因,这因类型推断错误而失败。将其更改为工作,就像.entrySetkeySetkey -> keytoMap()(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()Map.forEach()


推荐