我想要做的是在下面的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()));
答案 0 :(得分:63)
将条件放入lambda本身,例如
animalMap.entrySet().stream()
.forEach(
pair -> {
if (pair.getValue() != null) {
myMap.put(pair.getKey(), pair.getValue());
} else {
myList.add(pair.getKey());
}
}
);
当然,这假设两个集合(myMap
和myList
)在上面的代码之前被声明和初始化。
更新:使用Map.forEach
可以缩短代码,提高效率和可读性,如Jorn Vernee所建议:
animalMap.forEach(
(key, value) -> {
if (value != null) {
myMap.put(key, value);
} else {
myList.add(key);
}
}
);
答案 1 :(得分:11)
在大多数情况下,当您发现自己在Stream上使用forEach
时,您应该重新考虑是否使用适合您工作的工具,或者您是否以正确的方式使用它。
通常,您应该寻找适合您想要实现的目标或适当的收集器的终端操作。现在,有一些收集器用于生成Map
和List
s,但没有开箱即用的收集器,用于根据谓词组合两个不同的收集器。
现在,this answer包含一个用于组合两个收集器的收集器。使用此收集器,您可以实现
任务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;
但也许,你可以用更简单的方式解决这个特定的任务。其中一个结果与输入类型匹配;它只是剥离了映射到null
的条目的相同地图。如果您的原始地图是可变的,之后您不需要它,您只需收集列表并从原始地图中删除这些键,因为它们是互斥的:
List<KeyType> myList=animalMap.entrySet().stream()
.filter(pair -> pair.getValue() == null)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
animalMap.keySet().removeAll(myList);
请注意,即使没有其他键列表,您也可以删除null
的映射:
animalMap.values().removeIf(Objects::isNull);
或
animalMap.values().removeAll(Collections.singleton(null));
如果您不能(或不想)修改原始地图,则仍然存在没有自定义收集器的解决方案。如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());
最重要的是,不要忘记普通的Collection操作,您不必使用新的Stream API做任何事情。
答案 2 :(得分:7)
使用stream().forEach(..)
调用add
内的put
或forEach
来解决问题(这样就可以改变外部myMap
或myList
如果有人并行转换流并且您正在修改的集合不是线程安全的,那么您可以轻松地运行并发问题。
您可以采取的一种方法是首先对原始地图中的条目进行分区。完成后,抓取相应的条目列表并在相应的地图和列表中收集它们。
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());
...或者如果您想在一次传递中执行它,请实现自定义收集器(假设存在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;
});
}
用法:
Tuple2<Map<K, V>, List<K>> pair =
animalMap.entrySet().parallelStream().collect(customCollector());
如果需要,您可以更多地调整它,例如通过提供谓词作为参数。
答案 3 :(得分:5)
我认为在Java 9中可行:
animalMap.entrySet().stream()
.forEach(
pair -> Optional.ofNullable(pair.getValue())
.ifPresentOrElse(v -> myMap.put(pair.getKey(), v), v -> myList.add(pair.getKey())))
);
需要ifPresentOrElse才能使用它。 (我认为for循环看起来更好。)