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



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

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
}

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

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

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

以下是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上运行它。我认为,这使得代码更干净一些,代价是必须从映射中获取值,而不是从映射条目中获取值。顺便说一下,我最初将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()将键和值作为单独的参数给出,而流只有一个值 - 您必须选择是使用键还是映射条目作为该值。在缺点方面,这缺乏其他方法的丰富,流淌的优点。:-)

这样的通用解决方案

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

Guava的函数Maps.transformValues就是你要找的,它与lambda表达式配合得很好:

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

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

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

增强标准流 API 的 My StreamEx 库提供了一个更适合转换映射的EntryStream类:

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

出于学习目的而始终存在的另一种方法是通过 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;}
               ));

如果您不介意使用第三方库,我的 cyclops-react lib 具有所有 JDK 集合类型的扩展,包括 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);

尽管可以重新映射流collect部分中的键或/和值,如其他答案所示,但我认为它应该属于map部分,因为该函数旨在转换流中的数据。其次,它应该易于重复,而不会带来额外的复杂性。可以使用自 Java 6 以来已经可用的 SimpleEntry 对象。

使用爪哇 8

import java.util.AbstractMap.SimpleEntry;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
public class App {
    public static void main(String[] args) {
        Map<String, String> x;
        Map<String, Integer> y = x.entrySet().stream()
                .map(entry -> new SimpleEntry<>(entry.getKey(), Integer.parseInt(entry.getValue())))
                .collect(Collectors.toMap(Entry::getKey, Entry::getValue));
    }
}

使用爪哇 9+

随着 Java 9 的发布,Map 接口中引入了一个静态方法,以便更轻松地创建一个条目,而无需实例化新的 SimpleEntry,如前面的示例所示。

import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
public class App {
    public static void main(String[] args) {
        Map<String, String> x;
        Map<String, Integer> y = x.entrySet().stream()
                .map(entry -> Map.entry((entry.getKey(), Integer.parseInt(entry.getValue())))
                .collect(Collectors.toMap(Entry::getKey, Entry::getValue));
    }
}

一个声明式的、更简单的Java8+解决方案是:

yourMap.replaceAll((key, val( -> computeNewVal(;

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

相关内容

  • 没有找到相关文章

最新更新