使用流转换和过滤Java Map

Pau*_*l I 35 java java-8 java-stream collectors

我有一个我想要转换和过滤的Java Map.作为一个简单的例子,假设我想将所有值转换为整数然后删除奇数条目.

Map<String, String> input = new HashMap<>();
input.put("a", "1234");
input.put("b", "2345");
input.put("c", "3456");
input.put("d", "4567");

Map<String, Integer> output = input.entrySet().stream()
        .collect(Collectors.toMap(
                Map.Entry::getKey,
                e -> Integer.parseInt(e.getValue())
        ))
        .entrySet().stream()
        .filter(e -> e.getValue() % 2 == 0)
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));


System.out.println(output.toString());
Run Code Online (Sandbox Code Playgroud)

这是正确的,并产生: {a=1234, c=3456}

但是,我不禁想知道是否有办法避免.entrySet().stream()两次打电话.

有没有办法可以执行转换和过滤操作,.collect()最后只调用 一次?

Tun*_*aki 41

是的,您可以将每个条目映射到另一个临时条目,该条目将保存密钥和解析的整数值.然后,您可以根据其值过滤每个条目.

Map<String, Integer> output =
    input.entrySet()
         .stream()
         .map(e -> new AbstractMap.SimpleEntry<>(e.getKey(), Integer.valueOf(e.getValue())))
         .filter(e -> e.getValue() % 2 == 0)
         .collect(Collectors.toMap(
             Map.Entry::getKey,
             Map.Entry::getValue
         ));
Run Code Online (Sandbox Code Playgroud)

请注意,我使用Integer.valueOf而不是parseInt因为我们实际上想要一个盒装int.


如果您有幸使用StreamEx库,您可以非常简单地完成:

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

  • @Nambari我不明白为什么它是"hacky".它只是一个地图过滤器.如果它是`AbstractMap.SimpleEntry`的显式使用,你可以创建另一个`Pair`但我觉得这是合适的,因为我们已经处理了地图. (3认同)
  • @Nambari,请注意StreamEx在内部做同样的事情,它只是一个语法糖. (2认同)

Hol*_*ger 12

以较小的开销解决问题的一种方法是将映射和过滤向下移动到收集器.

Map<String, Integer> output = input.entrySet().stream().collect(
    HashMap::new,
    (map,e)->{ int i=Integer.parseInt(e.getValue()); if(i%2==0) map.put(e.getKey(), i); },
    Map::putAll);
Run Code Online (Sandbox Code Playgroud)

这不需要创建中间Map.Entry实例,甚至更好,将int值的装箱推迟到实际添加到值时的值Map,这意味着过滤器拒绝的值根本没有装箱.

与此相比Collectors.toMap(…),操作也通过使用Map.put而不是Map.merge事先知道我们不必处理关键冲突来简化操作.

但是,只要您不想使用并行执行,您也可以考虑普通循环

HashMap<String,Integer> output=new HashMap<>();
for(Map.Entry<String, String> e: input.entrySet()) {
    int i = Integer.parseInt(e.getValue());
    if(i%2==0) output.put(e.getKey(), i);
}
Run Code Online (Sandbox Code Playgroud)

或内部迭代变体:

HashMap<String,Integer> output=new HashMap<>();
input.forEach((k,v)->{ int i = Integer.parseInt(v); if(i%2==0) output.put(k, i); });
Run Code Online (Sandbox Code Playgroud)

后者非常紧凑,至少与所有其他有关单螺纹性能的变体相同.

  • @Jeffrey Bosboom:是的,好的旧`for`循环还活着.虽然在map的情况下,我喜欢使用`Map.forEach`方法用于较小的循环,因为`(k,v) - >`比声明`Map.Entry <PossiblyLongKeyType,PossiblyLongValueType>`变量更好.可能是另外两个变量的实际键和值... (2认同)

shm*_*sel 5

番石榴是您的朋友:

Map<String, Integer> output = Maps.filterValues(Maps.transformValues(input, Integer::valueOf), i -> i % 2 == 0);
Run Code Online (Sandbox Code Playgroud)

请记住,output是一个转变,过滤视图input。如果要独立操作它们,则需要进行复制。