Java 用谓词限制流

Java 用谓词限制流,java,java-8,java-stream,Java,Java 8,Java Stream,是否存在一个Java8流操作来限制潜在的无限流,直到第一个元素无法匹配谓词 在Java9中,我们可以使用takeWhile来打印所有小于10的数字,如下例所示 IntStream .iterate(1, n -> n + 1) .takeWhile(n -> n < 10) .forEach(System.out::println); 由于Java8中没有这样的操作,一般来说,实现它的最佳方法是什么 对于Java 8流,这样的操作应该是可能的,但它不一

是否存在一个Java8流操作来限制潜在的无限流,直到第一个元素无法匹配谓词

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

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

由于Java8中没有这样的操作,一般来说,实现它的最佳方法是什么

对于Java 8流,这样的操作应该是可能的,但它不一定能有效地完成——例如,您不一定能并行化这样的操作,因为您必须按顺序查看元素

API并没有提供一种简单的方法来实现这一点,但最简单的方法可能是采用Stream.iterator,将迭代器包装成一个take while实现,然后返回到一个拆分器,再返回到一个流。或者-也许-包装拆分器,尽管在这个实现中它不能再被拆分了

下面是一个未经测试的takeWhile在拆分器上的实现:

更新:Java9流现在提供了一个方法

不需要黑客或其他解决方案。就用那个

我相信这可以大大改进: 也许有人能让它线程安全

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
下面是一个在ints上完成的版本-如问题中所述

用法:

StreamUtil.takeWhile(IntStream.iterate(1, n -> n + 1), n -> n < 10);
takeWhile是由提供的功能之一


我有另一个快速解决方案,通过实施这一点,这实际上是非常不干净的,但你明白了:

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();
            }
        };
    }
}
allMatch是一个短路功能,因此您可以使用它停止处理。主要的缺点是你必须做两次测试:一次是看你是否应该处理它,另一次是看是否继续进行

IntStream
    .iterate(1, n -> n + 1)
    .peek(n->{if (n<10) System.out.println(n);})
    .allMatch(n->n < 10);
您可以使用java8+


下面是我仅使用Java流库的尝试

        IntStream.iterate(0, i -> i + 1)
        .filter(n -> {
                if (n < 10) {
                    System.out.println(n);
                    return false;
                } else {
                    return true;
                }
            })
        .findAny();
jdk9中添加了takeWhile和dropWhile操作。您的示例代码

IntStream
    .iterate(1, n -> n + 1)
    .takeWhile(n -> n < 10)
    .forEach(System.out::println);
在JDK 9下编译和运行时,它的行为将完全符合您的预期

JDK9已经发布。可在此处下载:。

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

IntStreamEx.iterate(1, n -> n + 1)
           .takeWhile(n -> n < 10)
           .forEach(System.out::println);
    assertEquals(newArrayList(1, 2, 3), takeWhile(newArrayList(1, 2, 3, 4, 3, 2, 1), i -> i < 4));
去图书馆。它提供了您想要的确切API以及更多:

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

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

除非通过短路终端操作,否则不能中止流,这将使一些流值无论其值如何都未处理。但如果您只是想避免对流进行操作,则可以向流中添加变换和过滤器:

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,这样所有后续的事情都会被过滤掉,而不管它们的值如何。但即使不是这样,您也可以通过从流中筛选出您不想处理的值来节省大量(如果不是全部)处理。

实际上,在Java 8中有两种方法可以做到这一点,而无需任何额外的库或使用Java 9

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

IntStream.iterate(2, (i) -> i + 2).peek(System.out::println).allMatch(i -> i < 20);
两种情况下的输出都是:

2
4
6
8
10
12
14
16
18
20

还没有人提到任何匹配。这就是写这篇文章的原因。

如果你有不同的问题,可能需要不同的解决方案,但对于你当前的问题,我只会选择:

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

这是从JDK 9 java.util.stream.stream.takeWhilePredicate复制的源代码。为了使用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);
}

甚至我也有类似的要求——调用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;
}

可能有点离题,但这是我们为列表而不是流所做的

首先,您需要有一个take-util方法。此方法采用前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);
    }
}
现在,基于take编写takeWhile方法将相当简单

它的工作原理如下:

IntStreamEx.iterate(1, n -> n + 1)
           .takeWhile(n -> n < 10)
           .forEach(System.out::println);
    assertEquals(newArrayList(1, 2, 3), takeWhile(newArrayList(1, 2, 3, 4, 3, 2, 1), i -> i < 4));

此实现将部分迭代列表几次,但不会添加附加^2操作。希望可以接受

如果您知道要执行的重复次数,您可以

IntStream
          .iterate(1, n -> n + 1)
          .limit(10)
          .forEach(System.out::println);
可以使用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);

可能有用的信息:相关:我想知道架构师如何能够在没有遇到这个用例的情况下完成我们实际使用它的目的。从Java8STR开始

eams实际上只对现有的数据结构有用:-/另请参见Java9,编写IntStream.iterate1,n->nn+1.forEachSystem.out::print会更容易;理论上,用无状态谓词并行takeWhile很容易。假设谓词在额外执行几次后不会抛出或产生副作用,则以并行批处理方式评估条件。问题是在Streams使用的递归分解fork/join框架的上下文中进行。事实上,流的效率非常低。如果流没有那么专注于自动并行,它会好得多。只有一小部分可以使用流的地方需要并行性。此外,如果Oracle如此关注性能,他们本可以让JVM JIT自动矢量化,并获得更大的性能提升,而不会打扰开发人员。现在,这是自动并行的正确做法。现在Java 9发布了,您应该更新这个答案。不,@Radiodef。这个问题是专门针对Java 8解决方案提出的。在一开始给出方法名时,我觉得这似乎不直观,但This Stream.allMatch是一个。因此,即使在无限流(如IntStream.iterate)上,这也将完成。当然,回顾过去,这是一个合理的优化。这很好,但我认为它不能很好地传达出它的意图是偷看的主体。如果我在下个月遇到它,我会花一分钟的时间想为什么我之前的程序员检查了If allMatch,然后忽略了答案。这种解决方案的缺点是它返回一个布尔值,因此您无法像通常那样收集流的结果。直接链接到JDK9流的预览文档,使用takeWhile/dropWhile:@LukasEder takeWhile和dropWhile非常普遍,出现在Scala、Python、Groovy、Ruby、Haskell和Clojure中。跳跃和极限的不对称是不幸的。也许skip和limit应该被称为drop and take,但除非你已经熟悉Haskell,否则它们就没有那么直观了。@StuartMarks:我知道dropXXX和takeXXX是比较流行的术语,但我个人可以使用SQL风格的limitXXX和skipXXX。我发现这种新的不对称性比单独选择术语更令人困惑…:顺便说一句:Scala也有dropint和TakeInt。让我在生产中升级到Jdk 9。许多开发人员仍然使用Jdk8,这样的功能应该从一开始就包含在Streams中。IntStream.iterate1,n->n+1.takeWhilen->n<10可以简化为IntStream.iterate1,n->n<10,n->n+1您正在提前评估整个流!如果电流不相等,你会得到一个无止境的循环。即使您随后申请,例如,limit1。这比“不干净”更糟糕。一些匿名评分员在没有说明原因的情况下贬低了我的答案,这太糟糕了。所以我和其他读者都不知道我的答案出了什么问题。在没有理由的情况下,我会认为他们的批评是无效的,而我的回答是正确的。你的回答并不能解决OPS问题,它处理的是无限的流。它似乎也不必要地使事情复杂化,因为您可以在过滤器调用本身中编写条件,而不需要映射。这个问题已经有了一个示例代码,只要尝试将您的答案应用于该代码,您就会看到程序将永远循环。为什么您没有为StreamEx类实现它?@Someguy我实现了它。System.out.println是一个副作用。虽然这可能回答了作者的问题,但它缺少一些解释性的词语和指向文档的链接。如果没有一些短语,原始代码片段就没有多大帮助。你也会发现这很有帮助。请编辑您的答案。这应该是公认的答案,如果它一直有效的话
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);
    }
}
    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));
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));
IntStream
          .iterate(1, n -> n + 1)
          .limit(10)
          .forEach(System.out::println);
    IntStream.iterate(1, n -> n + 1)
    .peek(System.out::println) //it will be executed 9 times
    .filter(n->n>=9)
    .findAny();
    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);