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

时间:2016-06-24 19:48:05

标签: 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()));

4 个答案:

答案 0 :(得分:63)

将条件放入lambda本身,例如

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

当然,这假设两个集合(myMapmyList)在上面的代码之前被声明和初始化。

更新:使用Map.forEach可以缩短代码,提高效率和可读性,如Jorn Vernee所建议:

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

答案 1 :(得分:11)

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

通常,您应该寻找适合您想要实现的目标或适当的收集器的终端操作。现在,有一些收集器用于生成MapList 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内的putforEach来解决问题(这样就可以改变外部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());

...或者如果您想在一次传递中执行它,请实现自定义收集器(假设存在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循环看起来更好。)