如何在Java 8 stream forEach中使用if-else逻辑

use*_*533 41 java java-8 java-stream

我想要做的是在下面的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()));
Run Code Online (Sandbox Code Playgroud)

Ale*_*rov 74

只需将条件放入lambda本身,例如

animalMap.entrySet().stream()
        .forEach(
                pair -> {
                    if (pair.getValue() != null) {
                        myMap.put(pair.getKey(), pair.getValue());
                    } else {
                        myList.add(pair.getKey());
                    }
                }
        );
Run Code Online (Sandbox Code Playgroud)

当然,这假设在上面的代码片段之前声明并初始化了两个集合(myMapmyList).


更新:使用Map.forEach使代码更短,加上更高效和可读性,正如Jorn Vernee所建议:

    animalMap.forEach(
            (key, value) -> {
                if (value != null) {
                    myMap.put(key, value);
                } else {
                    myList.add(key);
                }
            }
    );
Run Code Online (Sandbox Code Playgroud)

  • 您可以使用```Map.forEach```,它会更简洁一些. (4认同)
  • @JornVernee,非常感谢你的暗示. (2认同)
  • @ShivangAgarwal,这超出了这个问题的范围,也非常微不足道.使用空检查污染答案是没有意义的.请注意,`myMap`和`myList`也可以为null. (2认同)

Hol*_*ger 11

在大多数情况下,当您发现自己forEach在Stream上使用时,您应该重新考虑是否使用适合您工作的工具,或者您是否以正确的方式使用它.

通常,您应该寻找适合您想要实现的目标或适当的收集器的终端操作.现在,有一些收藏家用于制作Maps和Lists,但没有开箱即用的收藏家,根据谓词组合两个不同的收藏家.

现在,这个答案包含一个收集器,用于组合两个收集器.使用此收集器,您可以实现任务

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;
Run Code Online (Sandbox Code Playgroud)

但也许,您可以通过更简单的方式解决此特定任务.其中一个结果与输入类型匹配; 它只是剥离了映射到的条目的相同地图null.如果您的原始地图是可变的并且之后不需要它,您可以只收集列表并从原始地图中删除这些键,因为它们是互斥的:

List<KeyType> myList=animalMap.entrySet().stream()
    .filter(pair -> pair.getValue() == null)
    .map(Map.Entry::getKey)
    .collect(Collectors.toList());

animalMap.keySet().removeAll(myList);
Run Code Online (Sandbox Code Playgroud)

请注意,null即使没有其他键列表,您也可以删除映射:

animalMap.values().removeIf(Objects::isNull);
Run Code Online (Sandbox Code Playgroud)

要么

animalMap.values().removeAll(Collections.singleton(null));
Run Code Online (Sandbox Code Playgroud)

如果您不能(或不想)修改原始地图,则仍然存在没有自定义收集器的解决方案.正如Alexis C.的回答所示,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());
Run Code Online (Sandbox Code Playgroud)

最重要的是,不要忘记普通的Collection操作,你不必使用新的Stream API做任何事情.

  • @Marco Altieri:这取决于实际问题.实际上问的问题是关于*Stream API*,接受的答案没有真正回答,因为最后,`forEach`只是`for`循环的替代语法.例如,`Map.forEach(...)`变量不能并行运行,`entrySet().stream().forEach(...)`variant在并行运行时会破坏性.当您想要使用Stream API并了解如何正确使用它时,您必须使用Alexis C的答案或我的答案.一旦你明白了,你就不会觉得难以理解...... (5认同)

Ale*_* C. 7

通过stream().forEach(..)调用addput内部调用forEach(所以你改变外部myMapmyList实例)的问题是,如果有人并行转换流并且您正在修改的集合不是线程安全的,那么您可以轻松地运行并发问题.

您可以采取的一种方法是首先对原始地图中的条目进行分区.完成后,获取相应的条目列表并在相应的地图和列表中收集它们.

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());
Run Code Online (Sandbox Code Playgroud)

...或者如果您想在一次传递中执行它,请实现自定义收集器(假设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;
            });
}
Run Code Online (Sandbox Code Playgroud)

用法:

Tuple2<Map<K, V>, List<K>> pair = 
    animalMap.entrySet().parallelStream().collect(customCollector());
Run Code Online (Sandbox Code Playgroud)

如果需要,可以更多地调整它,例如通过提供谓词作为参数.


use*_*629 6

我认为在Java 9中是可能的:

animalMap.entrySet().stream()
                .forEach(
                        pair -> Optional.ofNullable(pair.getValue())
                                .ifPresentOrElse(v -> myMap.put(pair.getKey(), v), v -> myList.add(pair.getKey())))
                );
Run Code Online (Sandbox Code Playgroud)

但是需要ifPresentOrElse使其起作用。(我认为for循环看起来更好。)