如何在 Java 8 流 forEach 中使用 if-else 逻辑
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/38021061/
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
How to use if-else logic in Java 8 stream forEach
提问by user3768533
What I want to do is shown below in 2 stream calls. I want to split a collection into 2 new collections based on some condition. Ideally I want to do it in 1. I've seen conditions used for the .map function of streams, but couldn't find anything for the forEach. What is the best way to achieve what I want?
我想要做的显示在下面的 2 个流调用中。我想根据某些条件将一个集合拆分为 2 个新集合。理想情况下,我想在 1 中做到这一点。我已经看到了用于流的 .map 函数的条件,但找不到 forEach 的任何内容。实现我想要的最好方法是什么?
animalMap.entrySet().stream()
.filter(pair-> pair.getValue() != null)
.forEach(pair-> myMap.put(pair.getKey(), pair.getValue()));
animalMap.entrySet().stream()
.filter(pair-> pair.getValue() == null)
.forEach(pair-> myList.add(pair.getKey()));
采纳答案by Alex Shesterov
Just put the condition into the lambda itself, e.g.
只需将条件放入 lambda 本身,例如
animalMap.entrySet().stream()
.forEach(
pair -> {
if (pair.getValue() != null) {
myMap.put(pair.getKey(), pair.getValue());
} else {
myList.add(pair.getKey());
}
}
);
Of course, this assumes that both collections (myMap
and myList
) are declared and initialized prior to the above piece of code.
当然,这假设两个集合 (myMap
和myList
) 在上述代码段之前都已声明和初始化。
Update:using Map.forEach
makes the code shorter, plus more efficient and readable, as Jorn Verneekindly suggested:
更新:usingMap.forEach
使代码更短,更高效和可读,正如Jorn Vernee亲切的建议:
animalMap.forEach(
(key, value) -> {
if (value != null) {
myMap.put(key, value);
} else {
myList.add(key);
}
}
);
回答by Alexis C.
The problem by using stream().forEach(..)
with a call to add
or put
inside the forEach
(so you mutate the external myMap
or myList
instance) is that you can run easily into concurrency issues if someone turns the stream in parallel and the collection you are modifying is not thread safe.
使用stream().forEach(..)
调用add
或put
内部forEach
(因此您改变外部myMap
或myList
实例)的问题是,如果有人并行转换流并且您正在修改的集合不是线程安全的,您很容易遇到并发问题。
One approach you can take is to first partition the entries in the original map. Once you have that, grab the corresponding list of entries and collect them in the appropriate map and list.
您可以采用的一种方法是首先对原始地图中的条目进行分区。一旦你有了它,抓住相应的条目列表并将它们收集在适当的地图和列表中。
Map<Boolean, List<Map.Entry<K, V>>> partitions =
animalMap.entrySet()
.stream()
.collect(partitioningBy(e -> e.getValue() == null));
Map<K, V> myMap =
partitions.get(false)
.stream()
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
List<K> myList =
partitions.get(true)
.stream()
.map(Map.Entry::getKey)
.collect(toList());
... or if you want to do it in one pass, implement a custom collector (assuming a Tuple2<E1, E2>
class exists, you can create your own), e.g:
...或者如果您想一次性完成,请实现一个自定义收集器(假设Tuple2<E1, E2>
存在一个类,您可以创建自己的类),例如:
public static <K,V> Collector<Map.Entry<K, V>, ?, Tuple2<Map<K, V>, List<K>>> customCollector() {
return Collector.of(
() -> new Tuple2<>(new HashMap<>(), new ArrayList<>()),
(pair, entry) -> {
if(entry.getValue() == null) {
pair._2.add(entry.getKey());
} else {
pair._1.put(entry.getKey(), entry.getValue());
}
},
(p1, p2) -> {
p1._1.putAll(p2._1);
p1._2.addAll(p2._2);
return p1;
});
}
with its usage:
及其用法:
Tuple2<Map<K, V>, List<K>> pair =
animalMap.entrySet().parallelStream().collect(customCollector());
You can tune it more if you want, for example by providing a predicate as parameter.
如果需要,您可以对其进行更多调整,例如通过提供谓词作为参数。
回答by Holger
In most cases, when you find yourself using forEach
on a Stream, you should rethink whether you are using the right tool for your job or whether you are using it the right way.
在大多数情况下,当您发现自己forEach
在 Stream 上使用时,您应该重新考虑您是否正在为您的工作使用正确的工具,或者您是否以正确的方式使用它。
Generally, you should look for an appropriate terminal operation doing what you want to achieve or for an appropriate Collector. Now, there are Collectors for producing Map
s and List
s, but no out of-the-box collector for combining two different collectors, based on a predicate.
通常,您应该寻找合适的终端操作来完成您想要实现的目标或寻找合适的收集器。现在,有用于生成Map
s 和List
s 的收集器,但没有用于基于谓词组合两个不同收集器的开箱即用收集器。
Now, this answercontains a collector for combining two collectors. Using this collector, you can achieve the task as
现在,这个答案包含一个用于组合两个收集器的收集器。使用此收集器,您可以完成任务
Pair<Map<KeyType, Animal>, List<KeyType>> pair = animalMap.entrySet().stream()
.collect(conditional(entry -> entry.getValue() != null,
Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue),
Collectors.mapping(Map.Entry::getKey, Collectors.toList()) ));
Map<KeyType,Animal> myMap = pair.a;
List<KeyType> myList = pair.b;
But maybe, you can solve this specific task in a simpler way. One of you results matches the input type; it's the same map just stripped off the entries which map to null
. If your original map is mutable and you don't need it afterwards, you can just collect the list and remove these keys from the original map as they are mutually exclusive:
但也许,您可以以更简单的方式解决此特定任务。您的结果之一与输入类型匹配;这是同一张地图,只是去掉了映射到的条目null
。如果您的原始地图是可变的并且之后您不需要它,您可以只收集列表并从原始地图中删除这些键,因为它们是互斥的:
List<KeyType> myList=animalMap.entrySet().stream()
.filter(pair -> pair.getValue() == null)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
animalMap.keySet().removeAll(myList);
Note that you can remove mappings to null
even without having the list of the other keys:
请注意,null
即使没有其他键的列表,您也可以删除映射:
animalMap.values().removeIf(Objects::isNull);
or
或者
animalMap.values().removeAll(Collections.singleton(null));
If you can't (or don't want to) modify the original map, there is still a solution without a custom collector. As hinted in Alexis C.'s answer, partitioningBy
is going into the right direction, but you may simplify it:
如果您不能(或不想)修改原始地图,则仍然有没有自定义收集器的解决方案。正如Alexis C.'s answer所暗示的那样,partitioningBy
正在朝着正确的方向前进,但您可以简化它:
Map<Boolean,Map<KeyType,Animal>> tmp = animalMap.entrySet().stream()
.collect(Collectors.partitioningBy(pair -> pair.getValue() != null,
Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
Map<KeyType,Animal> myMap = tmp.get(true);
List<KeyType> myList = new ArrayList<>(tmp.get(false).keySet());
The bottom line is, don't forget about ordinary Collection operations, you don't have to do everything with the new Stream API.
最重要的是,不要忘记普通的 Collection 操作,您不必使用新的 Stream API 做所有事情。
回答by user3337629
I think it's possible in Java 9:
我认为在 Java 9 中是可能的:
animalMap.entrySet().stream()
.forEach(
pair -> Optional.ofNullable(pair.getValue())
.ifPresentOrElse(v -> myMap.put(pair.getKey(), v), v -> myList.add(pair.getKey())))
);
Need the ifPresentOrElse for it to work though. (I think a for loop looks better.)
需要 ifPresentOrElse 才能工作。(我认为 for 循环看起来更好。)