2017-07-18 86 views
2

我想知道从java流设置布尔标志值的最佳做法是什么。下面是我想要做的一个例子:在Java 8 Stream中设置一个布尔标志

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类似,但我试图在最后提取布尔值而不是设置变量。

+0

这不是建议,它是一种语言限制。 – shmosel

+0

*也可以将'flag'包装在'AtomicReference''或'AtomicBoolean'中? – shmosel

+2

你的第一个解决方案看起来非常好:检查isEmpty看起来是正确的方法。 –

回答

8

不要玷污你的生产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)两次,无条件地。


如果具备条件前述若干中间步骤,就可以收集到中间像List

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 
*/; 
+0

我向影响您答案的流式代码添加了另一条评论。条件正在处理链中间进行评估,而不是在最初阅读发布代码时开始评估。 – mitch

+1

呃,即便如此。将中间过滤的结果存储在中间列表中,检查它是否为空,并继续从该列表中流出。 –

+0

这很有道理。 @Holger我会接受,如果你更新你的答案。 – mitch

1

检查的布尔标志分别

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(); 
+0

我在影响您的答案的流式代码中添加了另一条注释行。条件正在处理链中间进行评估,而不是在最初阅读发布代码时开始评估。 – mitch

2

编辑:(基于Holger's comment below

我只是为了历史的目的才让这个答案;)这是我试图用Iterator来解决问题,虽然Spliterator好得多。这个答案不是100%错误的,但是当流被转换为Iterator时,支持流的分割器的特性(即SIZED,ORDERED等)将丢失。请参阅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可以使用。

+0

我知道你不同意这一点,但我只是把它放到一个'CHM'中作为*副作用* ... – Eugene

+1

我已经在我的答案中加入了类似的方法,但是,使用'Spliterator'而不是'Iterator',所以不需要猜测所需的适当特性,并且如果有大小估计的话,它也不会丢失。 – Holger

+0

@Eugene我知道,我知道...:D –