使用流转换和过滤 Java Map
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/35486826/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Transform and filter a Java Map with streams
提问by Paul I
I have a Java Map that I'd like to transform and filter. As a trivial example, suppose I want to convert all values to Integers then remove the odd entries.
我有一个想要转换和过滤的 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());
This is correct and yields: {a=1234, c=3456}
这是正确的并产生: {a=1234, c=3456}
However, I can't help but wonder if there's a way to avoid calling .entrySet().stream()
twice.
但是,我不禁想知道是否有办法避免调用.entrySet().stream()
两次。
Is there a way I can perform both transform and filter operations and call .collect()
only once at the end?
有没有一种方法可以同时执行转换和过滤操作,并且.collect()
最后只调用 一次?
采纳答案by Tunaki
Yes, you can map each entry to another temporary entry that will hold the key and the parsed integer value. Then you can filter each entry based on their value.
是的,您可以将每个条目映射到另一个临时条目,该条目将保存键和解析的整数值。然后您可以根据它们的值过滤每个条目。
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
));
Note that I used Integer.valueOf
instead of parseInt
since we actually want a boxed int
.
请注意,我使用了Integer.valueOf
而不是parseInt
因为我们实际上想要一个盒装的int
.
If you have the luxury to use the StreamExlibrary, you can do it quite simply:
如果你有幸使用StreamEx库,你可以很简单地做到:
Map<String, Integer> output =
EntryStream.of(input).mapValues(Integer::valueOf).filterValues(v -> v % 2 == 0).toMap();
回答by Federico Peralta Schaffner
You could use the Stream.collect(supplier, accumulator, combiner)
method to transform the entries and conditionally accumulate them:
您可以使用该Stream.collect(supplier, accumulator, combiner)
方法来转换条目并有条件地累积它们:
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}
Here, inside the accumulator, I'm using Optional
methods to apply both the transformation and the predicate, and, if the optional value is still present, I'm adding it to the map being collected.
在这里,在累加器中,我使用Optional
方法来应用转换和谓词,如果可选值仍然存在,我会将它添加到正在收集的映射中。
回答by Alex
Another way to do this is to remove the values you don't want from the transformed Map
:
另一种方法是从转换后的值中删除您不想要的值Map
:
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);
This assumes you want a mutable Map
as the result, if not you can probably create an immutable one from output
.
这假设您想要一个可变Map
的结果,如果不是,您可能可以从output
.
If you are transforming the values into the same type and want to modify the Map
in place this could be alot shorter with replaceAll
:
如果您要将值转换为相同的类型并希望Map
就地修改,这可能会短很多replaceAll
:
input.replaceAll((k, v) -> v + " example");
input.values().removeIf(v -> v.length() > 10);
This also assumes input
is mutable.
这也假设input
是可变的。
I don't recommend doing this because It will not work for all valid Map
implementations and may stop working for HashMap
in the future, but you can currently use replaceAll
and cast a HashMap
to change the type of the values:
我不建议这样做,因为它不适用于所有有效的Map
实现,并且HashMap
将来可能会停止工作,但您目前可以使用replaceAll
并强制转换 aHashMap
来更改值的类型:
((Map)input).replaceAll((k, v) -> Integer.parseInt((String)v));
Map<String, Integer> output = (Map)input;
output.values().removeIf(v -> v % 2 != 0);
This will also give you type safety warnings and if you try to retrieve a value from the Map
through a reference of the old type like this:
这也会为您提供类型安全警告,如果您尝试从Map
旧类型的引用中检索值,如下所示:
String ex = input.get("a");
It will throw a ClassCastException
.
它会抛出一个ClassCastException
.
You could move the first transform part into a method to avoid the boilerplate if you expect to use it alot:
如果您希望大量使用它,您可以将第一个转换部分移动到一个方法中以避免样板:
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));
}
And use it like this:
并像这样使用它:
Map<String, Integer> output = transformValues(input, Integer::parseInt, HashMap::new);
output.values().removeIf(v -> v % 2 != 0);
Note that the duplicate key exception can be thrown if, for example, the old
Map
is an IdentityHashMap
and the mapFactory
creates a HashMap
.
请注意,如果,例如,old
Map
is anIdentityHashMap
和 themapFactory
创建 a ,则可能会抛出重复键异常HashMap
。
回答by Holger
One way to solve the problem with much lesser overhead is to move the mapping and filtering down to the collector.
以更少的开销解决问题的一种方法是将映射和过滤向下移动到收集器。
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);
This does not require the creation of intermediate Map.Entry
instances and even better, will postpone the boxing of int
values to the point when the values are actually added to the Map
, which implies that values rejected by the filter are not boxed at all.
这不需要创建中间Map.Entry
实例,甚至更好的是,将int
值的装箱推迟到值实际添加到 时的点Map
,这意味着过滤器拒绝的值根本没有装箱。
Compared to what Collectors.toMap(…)
does, the operation is also simplified by using Map.put
rather than Map.merge
as we know beforehand that we don't have to handle key collisions here.
与do相比Collectors.toMap(…)
,操作也通过使用Map.put
而不是Map.merge
我们事先知道的我们不必在这里处理密钥冲突来简化。
However, as long as you don't want to utilize parallel execution you may also consider the ordinary loop
但是,只要您不想使用并行执行,您也可以考虑使用普通循环
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);
}
or the internal iteration variant:
或内部迭代变体:
HashMap<String,Integer> output=new HashMap<>();
input.forEach((k,v)->{ int i = Integer.parseInt(v); if(i%2==0) output.put(k, i); });
the latter being quite compact and at least on par with all other variants regarding single threaded performance.
后者非常紧凑,至少与有关单线程性能的所有其他变体相当。
回答by shmosel
Guava's your friend:
番石榴是你的朋友:
Map<String, Integer> output = Maps.filterValues(Maps.transformValues(input, Integer::valueOf), i -> i % 2 == 0);
Keep in mind that output
is a transformed, filtered viewof input
. You'll need to make a copy if you want to operate on them independently.
请记住,output
是一个转变,过滤视图中input
。如果您想独立操作它们,则需要制作一份副本。
回答by user_3380739
Here is code by AbacusUtil
这是AbacusUtil 的代码
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());
Declaration: I'm the developer of AbacusUtil.
声明: 我是 AbacusUtil 的开发者。