通过谓词限制流


187

是否存在Java 8流操作来限制(可能是无限的)Stream直到第一个元素与谓词不匹配?

在Java 9中,我们可以使用takeWhile下面的示例来打印所有小于10的数字。

IntStream
    .iterate(1, n -> n + 1)
    .takeWhile(n -> n < 10)
    .forEach(System.out::println);

由于Java 8中没有这样的操作,以一般方式实现它的最佳方法是什么?


1
可能有用的信息,位于:stackoverflow.com/q/19803058/248082
nobeh 2013年


我想知道架构师如何能够在不碰到这个用例的情况下,通过“我们可以真正其用于什么”。从Java 8的流只是对现有的数据结构实际上是有帮助的: - /
托尔比约恩Ravn的安徒生


使用Java 9,编写起来会更容易IntStream.iterate(1, n->n<10, n->n+1).forEach(System.out::print);
Marc Dzaebel

Answers:


81

这样的操作在Java 8中应该是可能的Stream,但不一定能有效地完成-例如,您不必并行化这样的操作,因为您必须按顺序查看元素。

该API并没有提供一种简便的方法,但是最简单的方法可能是将其Stream.iterator()打包,包装Iterator成具有“ take-while”的实现,然后返回到Spliterator然后是a Stream。或者-也许-包装Spliterator,尽管在此实现中它实际上不能再拆分了。

下面是一个未经测试的执行takeWhileSpliterator

static <T> Spliterator<T> takeWhile(
    Spliterator<T> splitr, Predicate<? super T> predicate) {
  return new Spliterators.AbstractSpliterator<T>(splitr.estimateSize(), 0) {
    boolean stillGoing = true;
    @Override public boolean tryAdvance(Consumer<? super T> consumer) {
      if (stillGoing) {
        boolean hadNext = splitr.tryAdvance(elem -> {
          if (predicate.test(elem)) {
            consumer.accept(elem);
          } else {
            stillGoing = false;
          }
        });
        return hadNext && stillGoing;
      }
      return false;
    }
  };
}

static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<? super T> predicate) {
   return StreamSupport.stream(takeWhile(stream.spliterator(), predicate), false);
}

8
从理论上讲,将takeWhile与无状态谓词并行化很容易。并行评估条件(假设该谓词在执行多次后不会抛出异常或产生副作用)。问题是在Streams使用的递归分解(fork / join框架)的上下文中进行的。实际上,流效率极低。
Aleksandr Dubinsky

91
如果流不那么关注自动魔术师并行性,那就更好了。仅在可以使用Streams的一小部分地方需要并行处理。此外,如果Oracle非常关心性能,他们可以使JVM JIT自动矢量化,并在不影响开发人员的情况下获得更大的性能提升。现在这是正确的自动魔术并行处理。
Aleksandr Dubinsky

Java 9发布后,您应该更新此答案。
Radiodef

4
不,@ Radiodef。这个问题专门针对Java 8解决方案。
Renato

145

操作takeWhiledropWhile已添加到JDK9。您的示例代码

IntStream
    .iterate(1, n -> n + 1)
    .takeWhile(n -> n < 10)
    .forEach(System.out::println);

在JDK 9下编译和运行时,其行为将完全符合您的预期。

JDK 9已发布。可在此处下载:http : //jdk.java.net/9/


3
直接链接到预览文档的JDK9流,与takeWhile/ dropWhiledownload.java.net/jdk9/docs/api/java/util/stream/Stream.html
万里

1
是否有任何理由,他们是所谓的takeWhiledropWhile,而不是limitWhileskipWhile,与现有API的一致性?
卢卡斯·埃德

10
@LukasEder takeWhiledropWhile相当广泛,在Scala中,Python和Groovy中,红宝石,哈斯克尔,和Clojure的发生。与不对称skiplimit不幸。也许skip并且limit应该被称为droptake,但是除非您已经熟悉Haskell,否则它们并不是那么直观。
斯图尔特·马克斯

3
@StuartMarks:据我所知,dropXXXtakeXXX更流行的术语,但我个人可以现场与多个SQL去年秋季limitXXXskipXXX。我发现这种新的不对称性比个人选择的术语更加令人困惑... :)(顺便说一句:Scala也有drop(int)and take(int)
Lukas Eder

1
是的,让我在生产中升级到Jdk 9。许多开发人员仍在使用Jdk8,从一开始就应该在Streams中包含这种功能。
wilmol

50

allMatch()是短路功能,因此您可以使用它来停止处理。主要缺点是您必须进行两次测试:一次查看是否应进行测试,再一次查看是否继续进行。

IntStream
    .iterate(1, n -> n + 1)
    .peek(n->{if (n<10) System.out.println(n);})
    .allMatch(n->n < 10);

5
起初,这对我来说似乎并不直观(给定方法名称),但是文档确认Stream.allMatch()是一种短路操作。因此,即使在像的无限流上,此操作也将完成IntStream.iterate()。当然,回想起来,这是明智的优化。
Bailey Parker

3
这很整洁,但我认为它的意图是的主体,因此传达的信息不太好peek。如果我下个月遇到它,我将花一分钟的时间想知道为什么我之前的程序员检查了if allMatch,然后忽略了答案。
约书亚·戈德堡

10
该解决方案的缺点是它返回一个布尔值,因此您不能像通常那样收集流的结果。
neXus

35

作为@StuartMarks answer的后续。我的StreamEx库具有takeWhile与当前JDK-9实现兼容的操作。在JDK-9下运行时,它将仅委托给JDK实现(通过MethodHandle.invokeExact它确实非常快)。在JDK-8下运行时,将使用“ polyfill”实现。因此,使用我的库可以像这样解决问题:

IntStreamEx.iterate(1, n -> n + 1)
           .takeWhile(n -> n < 10)
           .forEach(System.out::println);

为什么不为StreamEx类实现它?
Someguy

@Someguy我确实实现了它。
Tagir Valeev

14

takeWhileprotonpack库提供的功能之一。

Stream<Integer> infiniteInts = Stream.iterate(0, i -> i + 1);
Stream<Integer> finiteInts = StreamUtils.takeWhile(infiniteInts, i -> i < 10);

assertThat(finiteInts.collect(Collectors.toList()),
           hasSize(10));

11

更新:Java 9 Stream现在带有takeWhile方法。

无需黑客或其他解决方案。只需使用它!


我敢肯定,可以在以下方面进行很大的改进:(也许有人可以使其成为线程安全的)

Stream<Integer> stream = Stream.iterate(0, n -> n + 1);

TakeWhile.stream(stream, n -> n < 10000)
         .forEach(n -> System.out.print((n == 0 ? "" + n : "," + n)));

可以肯定的说...不是很优雅-但它可以工作〜:D

class TakeWhile<T> implements Iterator<T> {

    private final Iterator<T> iterator;
    private final Predicate<T> predicate;
    private volatile T next;
    private volatile boolean keepGoing = true;

    public TakeWhile(Stream<T> s, Predicate<T> p) {
        this.iterator = s.iterator();
        this.predicate = p;
    }

    @Override
    public boolean hasNext() {
        if (!keepGoing) {
            return false;
        }
        if (next != null) {
            return true;
        }
        if (iterator.hasNext()) {
            next = iterator.next();
            keepGoing = predicate.test(next);
            if (!keepGoing) {
                next = null;
            }
        }
        return next != null;
    }

    @Override
    public T next() {
        if (next == null) {
            if (!hasNext()) {
                throw new NoSuchElementException("Sorry. Nothing for you.");
            }
        }
        T temp = next;
        next = null;
        return temp;
    }

    public static <T> Stream<T> stream(Stream<T> s, Predicate<T> p) {
        TakeWhile tw = new TakeWhile(s, p);
        Spliterator split = Spliterators.spliterator(tw, Integer.MAX_VALUE, Spliterator.ORDERED);
        return StreamSupport.stream(split, false);
    }

}

8

您可以使用java8 + rxjava

import java.util.stream.IntStream;
import rx.Observable;


// Example 1)
IntStream intStream  = IntStream.iterate(1, n -> n + 1);
Observable.from(() -> intStream.iterator())
    .takeWhile(n ->
          {
                System.out.println(n);
                return n < 10;
          }
    ).subscribe() ;


// Example 2
IntStream intStream  = IntStream.iterate(1, n -> n + 1);
Observable.from(() -> intStream.iterator())
    .takeWhile(n -> n < 10)
    .forEach( n -> System.out.println(n));

6

实际上,在Java 8中有两种方法可以实现此目的,而无需任何额外的库或使用Java 9。

如果要在控制台上打印2到20之间的数字,可以执行以下操作:

IntStream.iterate(2, (i) -> i + 2).peek(System.out::println).allMatch(i -> i < 20);

要么

IntStream.iterate(2, (i) -> i + 2).peek(System.out::println).anyMatch(i -> i >= 20);

在两种情况下,输出均为:

2
4
6
8
10
12
14
16
18
20

还没有人提及任何比赛。这就是这篇文章的原因。


5

这是从JDK 9 java.util.stream.Stream.takeWhile(Predicate)复制的源。为了使用JDK 8,有一些区别。

static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<? super T> p) {
    class Taking extends Spliterators.AbstractSpliterator<T> implements Consumer<T> {
        private static final int CANCEL_CHECK_COUNT = 63;
        private final Spliterator<T> s;
        private int count;
        private T t;
        private final AtomicBoolean cancel = new AtomicBoolean();
        private boolean takeOrDrop = true;

        Taking(Spliterator<T> s) {
            super(s.estimateSize(), s.characteristics() & ~(Spliterator.SIZED | Spliterator.SUBSIZED));
            this.s = s;
        }

        @Override
        public boolean tryAdvance(Consumer<? super T> action) {
            boolean test = true;
            if (takeOrDrop &&               // If can take
                    (count != 0 || !cancel.get()) && // and if not cancelled
                    s.tryAdvance(this) &&   // and if advanced one element
                    (test = p.test(t))) {   // and test on element passes
                action.accept(t);           // then accept element
                return true;
            } else {
                // Taking is finished
                takeOrDrop = false;
                // Cancel all further traversal and splitting operations
                // only if test of element failed (short-circuited)
                if (!test)
                    cancel.set(true);
                return false;
            }
        }

        @Override
        public Comparator<? super T> getComparator() {
            return s.getComparator();
        }

        @Override
        public void accept(T t) {
            count = (count + 1) & CANCEL_CHECK_COUNT;
            this.t = t;
        }

        @Override
        public Spliterator<T> trySplit() {
            return null;
        }
    }
    return StreamSupport.stream(new Taking(stream.spliterator()), stream.isParallel()).onClose(stream::close);
}

4

这是在整数上完成的版本-如问题中所述。

用法:

StreamUtil.takeWhile(IntStream.iterate(1, n -> n + 1), n -> n < 10);

这是StreamUtil的代码:

import java.util.PrimitiveIterator;
import java.util.Spliterators;
import java.util.function.IntConsumer;
import java.util.function.IntPredicate;
import java.util.stream.IntStream;
import java.util.stream.StreamSupport;

public class StreamUtil
{
    public static IntStream takeWhile(IntStream stream, IntPredicate predicate)
    {
        return StreamSupport.intStream(new PredicateIntSpliterator(stream, predicate), false);
    }

    private static class PredicateIntSpliterator extends Spliterators.AbstractIntSpliterator
    {
        private final PrimitiveIterator.OfInt iterator;
        private final IntPredicate predicate;

        public PredicateIntSpliterator(IntStream stream, IntPredicate predicate)
        {
            super(Long.MAX_VALUE, IMMUTABLE);
            this.iterator = stream.iterator();
            this.predicate = predicate;
        }

        @Override
        public boolean tryAdvance(IntConsumer action)
        {
            if (iterator.hasNext()) {
                int value = iterator.nextInt();
                if (predicate.test(value)) {
                    action.accept(value);
                    return true;
                }
            }

            return false;
        }
    }
}

2

去获取库AbacusUtil。它提供了所需的确切API以及更多:

IntStream.iterate(1, n -> n + 1).takeWhile(n -> n < 10).forEach(System.out::println);

声明:我是AbacusUtil的开发人员。


0

您不能通过中断终端操作来中止流,这将使某些流值不受处理,无论其值如何。但是,如果只想避免对流进行操作,则可以向该流添加转换和过滤器:

import java.util.Objects;

class ThingProcessor
{
    static Thing returnNullOnCondition(Thing thing)
    {    return( (*** is condition met ***)? null : thing);    }

    void processThings(Collection<Thing> thingsCollection)
    {
        thingsCollection.stream()
        *** regular stream processing ***
        .map(ThingProcessor::returnNullOnCondition)
        .filter(Objects::nonNull)
        *** continue stream processing ***
    }
} // class ThingProcessor

当事物满足某种条件时,它将事物流转换为null,然后过滤掉null。如果您愿意沉迷于副作用,则可以在遇到某些问题时将条件值设置为true,这样,所有后续事物都会被滤除,无论其价值如何。但是,即使不是这样,您也可以通过从不需要处理的流中过滤出值来节省很多(如果不是全部)处理。


anonymous脚的是,一些匿名评估者降低了我的回答而没有说出原因。因此,我和其他任何读者都不知道我的答案有什么问题。在没有理由的情况下,我会认为他们的批评无效,而我的回答正确无误。
马修(Matthew)

您回答的问题无法解决OPs问题,后者正在处理无限流。这似乎也不必要地使事情复杂化,因为您可以在filter()调用本身中编写条件,而无需map()。该问题已经有示例代码,只需尝试将答案应用于该代码,您将看到该程序将永远循环。
SenoCtar

0

即使我也有类似的要求-调用Web服务,如果失败,请重试3次。如果经过多次尝试后仍然失败,请发送电子邮件通知。经过大量搜索之后,它anyMatch()成为了救星。我的示例代码如下。在以下示例中,如果webServiceCall方法在第一次迭代本身中返回true,则流不会像我们调用那样进一步迭代anyMatch()。我相信,这就是您想要的。

import java.util.stream.IntStream;

import io.netty.util.internal.ThreadLocalRandom;

class TrialStreamMatch {

public static void main(String[] args) {        
    if(!IntStream.range(1,3).anyMatch(integ -> webServiceCall(integ))){
         //Code for sending email notifications
    }
}

public static boolean webServiceCall(int i){
    //For time being, I have written a code for generating boolean randomly
    //This whole piece needs to be replaced by actual web-service client code
    boolean bool = ThreadLocalRandom.current().nextBoolean();
    System.out.println("Iteration index :: "+i+" bool :: "+bool);

    //Return success status -- true or false
    return bool;
}

0

如果您知道将执行的重新排列的确切数量,则可以执行

IntStream
          .iterate(1, n -> n + 1)
          .limit(10)
          .forEach(System.out::println);

1
尽管这可以回答作者的问题,但它缺少一些解释性的文字和文档链接。没有一些短语,原始代码片段不是很有帮助。您可能还会发现如何写一个好的答案很有帮助。请修改您的答案。
hellow 18/09/24

0
    IntStream.iterate(1, n -> n + 1)
    .peek(System.out::println) //it will be executed 9 times
    .filter(n->n>=9)
    .findAny();

您可以使用mapToObj而不是peak来返回最终对象或消息

    IntStream.iterate(1, n -> n + 1)
    .mapToObj(n->{   //it will be executed 9 times
            if(n<9)
                return "";
            return "Loop repeats " + n + " times";});
    .filter(message->!message.isEmpty())
    .findAny()
    .ifPresent(System.out::println);

-2

如果您有其他问题,则可能需要不同的解决方案,但是对于您当前的问题,我只需考虑:

IntStream
    .iterate(1, n -> n + 1)
    .limit(10)
    .forEach(System.out::println);

-2

可能有点偏离主题,但这是我们的目标 List<T>而不是Stream<T>

首先,您需要一个takeutil方法。此方法具有第一个n要素:

static <T> List<T> take(List<T> l, int n) {
    if (n <= 0) {
        return newArrayList();
    } else {
        int takeTo = Math.min(Math.max(n, 0), l.size());
        return l.subList(0, takeTo);
    }
}

它就像 scala.List.take

    assertEquals(newArrayList(1, 2, 3), take(newArrayList(1, 2, 3, 4, 5), 3));
    assertEquals(newArrayList(1, 2, 3), take(newArrayList(1, 2, 3), 5));

    assertEquals(newArrayList(), take(newArrayList(1, 2, 3), -1));
    assertEquals(newArrayList(), take(newArrayList(1, 2, 3), 0));

现在写一个takeWhile基于take

static <T> List<T> takeWhile(List<T> l, Predicate<T> p) {
    return l.stream().
            filter(p.negate()).findFirst(). // find first element when p is false
            map(l::indexOf).        // find the index of that element
            map(i -> take(l, i)).   // take up to the index
            orElse(l);  // return full list if p is true for all elements
}

它是这样的:

    assertEquals(newArrayList(1, 2, 3), takeWhile(newArrayList(1, 2, 3, 4, 3, 2, 1), i -> i < 4));

此实现会部分迭代列表几次,但不会添加添加O(n^2)操作。希望这是可以接受的。


-3

通过实现这一点,我还有另一个快速的解决方案(实际上这是非常不洁的,但是您知道了):

public static void main(String[] args) {
    System.out.println(StreamUtil.iterate(1, o -> o + 1).terminateOn(15)
            .map(o -> o.toString()).collect(Collectors.joining(", ")));
}

static interface TerminatedStream<T> {
    Stream<T> terminateOn(T e);
}

static class StreamUtil {
    static <T> TerminatedStream<T> iterate(T seed, UnaryOperator<T> op) {
        return new TerminatedStream<T>() {
            public Stream<T> terminateOn(T e) {
                Builder<T> builder = Stream.<T> builder().add(seed);
                T current = seed;
                while (!current.equals(e)) {
                    current = op.apply(current);
                    builder.add(current);
                }
                return builder.build();
            }
        };
    }
}

2
您正在预先评估整个流!如果current永远不会.equals(e),您将得到无尽的循环。两者,即使您随后都申请了.limit(1)。这比“不洁”要糟糕得多。
查理

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.