使用流转换和过滤Java映射

使用流转换和过滤Java映射,java,java-8,java-stream,collectors,Java,Java 8,Java Stream,Collectors,我有一个Java映射,我想转换和过滤它。作为一个简单的例子,假设我想将所有值转换为整数,然后删除奇数项 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.en

我有一个Java映射,我想转换和过滤它。作为一个简单的例子,假设我想将所有值转换为整数,然后删除奇数项

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());
Map input=newhashmap();
输入。输入(“a”、“1234”);
投入。投入(“b”,“2345”);
输入。输入(“c”,“3456”);
投入。投入(“d”,“4567”);
映射输出=input.entrySet().stream()
.collect(collector.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());
这是正确的,并产生:
{a=1234,c=3456}

然而,我不禁想知道是否有办法避免调用
.entrySet().stream()
两次


是否有一种方法可以执行转换和筛选操作,并在结束时只调用一次
.collect()

是的,您可以将每个条目映射到另一个临时条目,该条目将保存密钥和解析的整数值。然后,您可以根据每个条目的值筛选它们

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
         ));
您可以使用该方法转换条目并有条件地累积它们:

Map<String, Integer> even = input.entrySet().stream().collect(
    HashMap::new,
    (m, e) -> Optional.ofNullable(e)
            .map(Map.Entry::getValue)
            .map(Integer::valueOf)
            .filter(i -> i % 2 == 0)
            .ifPresent(i -> m.put(e.getKey(), i)),
    Map::putAll);

System.out.println(even); // {a=1234, c=3456}
Map偶数=input.entrySet().stream().collect(
HashMap::新建,
(m,e)->可选。不可用(e)
.map(map.Entry::getValue)
.map(整数::valueOf)
.filter(i->i%2==0)
.ifPresent(i->m.put(e.getKey(),i)),
地图:putAll);
System.out.println(偶数);//{a=1234,c=3456}

这里,在累加器内部,我使用
Optional
方法应用转换和谓词,如果可选值仍然存在,我将其添加到正在收集的映射中。

另一种方法是从转换后的
映射中删除不需要的值:

Map<String, Integer> output = input.entrySet().stream()
        .collect(Collectors.toMap(
                Map.Entry::getKey,
                e -> Integer.parseInt(e.getValue()),
                (a, b) -> { throw new AssertionError(); },
                HashMap::new
         ));
output.values().removeIf(v -> v % 2 != 0);
这也假设
输入是可变的


我不建议这样做,因为它不适用于所有有效的
Map
实现,将来可能会停止对
HashMap
的工作,但您当前可以使用
replaceAll
并强制转换
HashMap
来更改值的类型:

((Map)input).replaceAll((k, v) -> Integer.parseInt((String)v));
Map<String, Integer> output = (Map)input;
output.values().removeIf(v -> v % 2 != 0);
它将抛出一个
ClassCastException


如果希望经常使用样板,可以将第一个变换部分移动到一个方法中以避免使用样板:

public static <K, VO, VN, M extends Map<K, VN>> M transformValues(
        Map<? extends K, ? extends VO> old, 
        Function<? super VO, ? extends VN> f, 
        Supplier<? extends M> mapFactory){
    return old.entrySet().stream().collect(Collectors.toMap(
            Entry::getKey, 
            e -> f.apply(e.getValue()), 
            (a, b) -> { throw new IllegalStateException("Duplicate keys for values " + a + " " + b); },
            mapFactory));
}
公共静态M值(

Map解决此问题的一种方法是将映射和过滤向下移动到收集器,开销要小得多

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);
或内部迭代变量:

HashMap<String,Integer> output=new HashMap<>();
input.forEach((k,v)->{ int i = Integer.parseInt(v); if(i%2==0) output.put(k, i); });
HashMap输出=新的HashMap();
input.forEach((k,v)->{inti=Integer.parseInt(v);if(i%2==0)output.put(k,i);});

后者是相当紧凑的,至少与所有关于单线程性能的变体相一致。

< P>您的朋友:

Map<String, Integer> output = Maps.filterValues(Maps.transformValues(input, Integer::valueOf), i -> i % 2 == 0);
Map output=Maps.filterValues(Maps.transformValues(input,Integer::valueOf),i->i%2==0);
请记住,
输出
是一个经过转换、过滤的
输入视图
。如果您想独立操作它们,您需要制作一个副本。

以下是代码

Map输入=N.asMap(“a”、“1234”、“b”、“2345”、“c”、“3456”、“d”、“4567”);
映射输出=流(输入)
.groupBy(e->e.getKey(),e->N.asInt(e.getValue())
.filter(e->e.getValue()%2==0)
.toMap(Map.Entry::getKey,Map.Entry::getValue);
N.println(output.toString());

声明:我是AbacusUtil的开发人员。

我认为这是不可能的。基于javadoc,“一个流应该只操作一次(调用中间或终端流操作)。这排除了,例如,“forked”流,其中同一源向两个或多个管道提供数据,或对同一流进行多次遍历。如果流实现检测到流正在被重用,则可能引发IllegalStateException。“@Namban这不是问题所在。虽然这是一种可能的黑客方式,但我觉得我们为了几行代码牺牲了性能和可读性,不是吗?@Nambari我不明白为什么会这么“黑客”。它只是一个映射过滤器。如果它是显式使用
AbstractMap.SimpleEntry
,您可以创建另一个
,但我觉得它在这里是合适的,因为我们已经在处理映射了。我使用“hacky”只是因为“临时条目”我们正在跟踪,可能不是正确的术语。我喜欢StreamEx解决方案。@Nambari,请注意,StreamEx在内部也做同样的事情,它只是一个语法糖。@TagirValeev:是的,我知道。当API不支持时,所有这些库只做语法糖。非常接近,但我不认为在这里使用
Optional
…@Holger这只是把所有的东西都放在一行。不管怎样,我还没有看到你的答案。胜利(如果我们变得非常微妙的话)可能是
可选的。ofNullable()
允许空键。我们同时在写。
可选的
链实际上不是一行(或非常大的一行)但是只有一个表达式。但是从一个特定的表达式大小开始,lambda语句所需的两个大括号似乎不再那么昂贵了……我支持你的普通循环建议。仅仅因为你可以使用streams并不意味着你应该这样做。@Jeffrey Bosboom:是的,好的
for
循环仍然存在。不过对于映射,我喜欢对较小的循环使用
Map.forEach
方法,因为
(k,v)->
比声明一个
Map.Entry
变量,可能还有另外两个变量用于实际的键和值…您的
“复制键”+a+“+b
消息具有误导性:
a
b
实际上是值,而不是键。@TagirValeev是的,我注意到了,但这与 Map<String, Integer> output = transformValues(input, Integer::parseInt, HashMap::new); output.values().removeIf(v -> v % 2 != 0);
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);
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);
}
HashMap<String,Integer> output=new HashMap<>();
input.forEach((k,v)->{ int i = Integer.parseInt(v); if(i%2==0) output.put(k, i); });
Map<String, Integer> output = Maps.filterValues(Maps.transformValues(input, Integer::valueOf), i -> i % 2 == 0);
Map<String, String> input = N.asMap("a", "1234", "b", "2345", "c", "3456", "d", "4567");

Map<String, Integer> output = Stream.of(input)
                          .groupBy(e -> e.getKey(), e -> N.asInt(e.getValue()))
                          .filter(e -> e.getValue() % 2 == 0)
                          .toMap(Map.Entry::getKey, Map.Entry::getValue);

N.println(output.toString());