使用流转换和过滤Java映射
我有一个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
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());