【问题标题】:Setting a boolean flag inside Java 8 Stream在 Java 8 Stream 中设置布尔标志
【发布时间】:2017-07-18 17:02:11
【问题描述】:

我想知道从 java 流中设置布尔标志值的最佳实践是什么。这是我想做的一个例子:

    List<Integer> list = Arrays.asList(1,2,3,4,5);
    boolean flag = false;
    List<Integer> newList = list.stream()
                                //many other filters, flatmaps, etc...
                                .filter(i -> {
                                    if(condition(i)){
                                        flag = true;
                                    }
                                    return condition(i);
                                })
                                //many other filters, flatmaps, etc...
                                .collect(Collectors.toList());
    //do some work with the new list and the flag

但是,这违反了语言限制“在 lambda 表达式中使用的变量应该是最终的或有效的最终”。我能想到一些解决方案,但我不确定哪个是最好的。我的第一个解决方案是将匹配condition 的元素添加到列表中并检查List::isEmpty。也可以将flag 包裹在AtomicReference 中。

请注意,我的问题与question 类似,但我试图在最后提取一个布尔值,而不是设置一个变量。

【问题讨论】:

  • 这不是建议,这是语言限制。
  • 也可以将flag 包裹在AtomicReference 中。AtomicBoolean?
  • 您的第一个解决方案看起来非常好:检查 isEmpty 似乎是正确的方法。
  • 修复它@shmosel
  • 任何阅读这篇文章的人都不应该认为这是一个“好主意”,即具有来自filter() 的副作用。 OP 应该发布更多代码,因为问题可能可以更优雅地解决并符合函数式编程最佳实践(例如使用findAny()

标签: java java-8 java-stream


【解决方案1】:

不要用完全不相关的任务来污染您生成newList 的任务。只需使用

boolean flag = list.stream().anyMatch(i -> condition(i));

后面是另一个流代码。

有两种典型的反对意见

  1. 但这是迭代两次。

    是的,但谁说重复 ArrayList 两次是个问题?不要试图避免多个流操作,除非你知道你确实有一个昂贵的遍历流源,比如一个外部文件。如果您有如此昂贵的来源,那么首先将元素收集到一个集合中可能会更容易,您可以遍历两次。

  2. 但它不止一次评估condition(…)

    嗯,实际上它对它的评估比你原来的代码少

    .filter(i -> {
        if(condition(i)){
            flag = true;
        }
        return condition(i);
    })
    

    anyMatch 在第一个匹配时停止,而您的原始谓词无条件地对每个元素计算 condition(i) 两次。


如果你在条件之前有几个中间步骤,你可以收集到一个中间Listlike

List<Integer> intermediate = list.stream()
    //many other filters, flatmaps, etc...
    .filter(i -> condition(i))
    .collect(Collectors.toList());
boolean flag = !intermediate.isEmpty();
List<Integer> newList = intermediate.stream()
    //many other filters, flatmaps, etc...
    .collect(Collectors.toList());

但通常情况下,中间步骤并不像乍一看那样昂贵。类似的中间步骤的性能特征在不同的流操作中可能会有所不同,具体取决于实际的终端操作。因此,在运行中执行这些步骤时,它可能仍然可以充分发挥作用:

boolean flag = list.stream()
    //many other filters, flatmaps, etc...
    .anyMatch(i -> condition(i));
List<Integer> newList = list.stream()
    //many other filters, flatmaps, etc...
    .filter(i -> condition(i))
    //many other filters, flatmaps, etc...
    .collect(Collectors.toList());

如果您担心代码重复本身,您仍然可以将通用代码放入流返回实用程序方法中。

只有在极少数情况下,进入低级 API 并像 this answer 一样窥视 Stream 是值得的。如果你这样做了,你不应该走Iterator的路线,这会丢失有关内容的元信息,而是使用Spliterator

Spliterator<Integer> sp = list.stream()
    //many other filters, flatmaps, etc...
    .filter(i -> condition(i))
    .spliterator();
Stream.Builder<Integer> first = Stream.builder();
boolean flag = sp.tryAdvance(first);
List<Integer> newList = Stream.concat(first.build(), StreamSupport.stream(sp, false))
    //many other filters, flatmaps, etc...
    .collect(Collectors.toList());

请注意,在所有这些情况下,如果flagfalse,您可以使用快捷方式,因为结果只能是一个空列表:

List<Integer> newList = !flag? Collections.emptyList():
/*
   subsequent stream operation
 */;

【讨论】:

  • 我在流式代码中添加了另一个注释行,这会影响您的答案。条件是在处理链的中间进行评估,而不是在最初读取的发布代码的开头。
  • 呃,即便如此。将中间过滤结果存储在中间 List 中,检查它是否为空,然后从该 List 继续流。
  • 这是有道理的。 @Holger 如果您更新答案,我会接受。
【解决方案2】:

编辑:(基于Holger's comment below

我在这里给出这个答案只是出于历史目的;)这是我用Iterator 解决问题的尝试,尽管Spliterator 更好。这个答案不是 100% 错误,但是当流转换为 Iterator 时,支持流的拆分器的特征(即SIZEDORDERED 等)会丢失。请参阅Holger's awesome answer 了解最佳方法,以及其他替代方案以及关于这是否值得努力的简短讨论。


如果您需要知道流管道的中间过滤条件是否匹配,您可能需要考虑将流转换为Iterator,检查是否迭代器有一个下一个元素,将该值存储为您的标志,然后从迭代器创建一个新流,最后继续流管道。

在代码中:

Iterator<Whatever> iterator = list.stream()
    // many other filters, flatmaps, etc...
    .filter(i -> condition(i))
    .iterator();

boolean flag = iterator.hasNext();

然后,从迭代器创建一个新的Stream

Stream<Whatever> stream = StreamSupport.stream(
    Spliterators.spliteratorUnknownSize(
        iterator, 
        Spliterator.NONNULL), // maybe Spliterator.ORDERED?
    false);

最后继续流管道:

List<Integer> newList = stream
    // many other filters, flatmaps, etc...
    .collect(Collectors.toList());

现在您可以使用 newListflag

【讨论】:

  • 我知道你不同意这一点,但我只是把它放到CHM 中作为副作用...
  • 我已经在我的答案中加入了类似的方法,但是,它使用Spliterator 而不是Iterator,因此无需猜测所需的适当特征,而且它没有如果有,请放宽尺寸估计。
  • @Eugene 我知道,我知道...:D
  • @Holger 你用Spliterator 做得很好。我没有使用它,因为tryAdvance 也消耗了第一个元素,所以我认为Iterator 在这种情况下会更好。现在很清楚,我显然错了,Spliterator 要好得多。没想到Stream.BuilderStream.concat 的做法,真是太聪明了。
【解决方案3】:

单独检查布尔标志

List<Integer> list = Arrays.asList(1,2,3,4,5);
List<Integer> newList = list.stream()
                            .filter(i -> condition(i))
                            //many other filters, flatmaps, etc...
                            .collect(Collectors.toList());

boolean flag = list.stream()
                     .filter(i -> condition(i))
                     .findAny()
                     .isPresent();

【讨论】:

  • 我在流式代码中添加了另一个注释行,这会影响您的答案。条件是在处理链的中间进行评估,而不是在最初读取的发布代码的开头。
猜你喜欢
  • 2015-07-14
  • 2011-05-26
  • 2011-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-27
  • 2019-04-10
相关资源
最近更新 更多