【问题标题】:Why is this java Stream operated upon twice?为什么这个 java Stream 被操作了两次?
【发布时间】:2016-01-08 12:59:02
【问题描述】:

Java 8 API 说:

直到管道的终端操作执行完毕,管道源的遍历才开始。

那么为什么会抛出以下代码:

java.lang.IllegalStateException: 流已经被操作 或关闭

Stream<Integer> stream = Stream.of(1,2,3);
stream.filter( x-> x>1 );
stream.filter( x-> x>2 ).forEach(System.out::print);

API 的第一个过滤操作不应该对 Stream 进行操作。

【问题讨论】:

    标签: java java-8 java-stream


    【解决方案1】:

    这是因为您忽略了filter 的返回值。

    Stream<Integer> stream = Stream.of(1,2,3);
    stream.filter( x-> x>1 ); // <-- ignoring the return value here
    stream.filter( x-> x>2 ).forEach(System.out::print);
    

    Stream.filter 返回一个 new Stream,其中包含与给定谓词匹配的此流的元素。但重要的是要注意它是一个新的 Stream。旧的过滤器添加到它时已对其进行了操作。但新的不是。

    引用StreamJavadoc:

    一个流只能被操作(调用一个中间或终端流操作)一次。

    在这种情况下,filter 是对旧 Stream 实例进行操作的中间操作。

    所以这段代码可以正常工作:

    Stream<Integer> stream = Stream.of(1,2,3);
    stream = stream.filter( x-> x>1 ); // <-- NOT ignoring the return value here
    stream.filter( x-> x>2 ).forEach(System.out::print);
    

    正如 Brian Goetz 所说,您通常会将这些调用链接在一起:

    Stream.of(1,2,3).filter( x-> x>1 )
                    .filter( x-> x>2 )
                    .forEach(System.out::print);
    

    【讨论】:

    • 所以对 Stream 进行操作与遍历 Stream 是不同的。我想对 Stream 进行操作会改变它的内部状态。
    • @ilias 是的,它确实改变了 Stream 的内部状态。
    【解决方案2】:

    filter() 方法使用流并返回另一个您在示例中忽略的 Stream 实例。

    filter 是一个中间操作,但你不能在同一个流实例上调用两次过滤器

    你的代码应该写成如下:

    Stream<Integer> stream = Stream.of(1,2,3);
                                   .filter( x-> x>1 )
                                   .filter( x-> x>2);
    stream.forEach(System.out::print);
    

    由于过滤器是一个中间操作,调用这些方法时“什么都不做”。调用forEach()方法时,所有工作都真正处理完毕

    【讨论】:

    • 我认为“什么都没做”是不正确的。当你调用第一个过滤器时“已经完成”了一些事情,这就是为什么当你调用同一个流的第二个过滤器时,它没有。
    • @MrinalK.Samanta 这就是我为什么要加引号的原因;)我的意思是它只是声明性的,此时没有真正进行过滤,但只有当调用像 forEach 这样的终端操作时
    【解决方案3】:

    Streams 上的文档说:

    “一个流应该只被操作(调用一个中间或终端流操作)一次。”

    您实际上可以在源代码中看到这一点。当您调用 filter 时,它会返回一个新的无状态操作,并在构造函数中传递当前管道实例 (this):

    @Override
    public final Stream<P_OUT> filter(Predicate<? super P_OUT> predicate) {
        Objects.requireNonNull(predicate);
        return new StatelessOp<P_OUT, P_OUT>(this, StreamShape.REFERENCE,
                                           StreamOpFlag.NOT_SIZED) {
            ....
    }
    

    构造函数调用最终调用AbstractPipeline构造函数,其设置如下:

    AbstractPipeline(AbstractPipeline<?, E_IN, ?> previousStage, int opFlags) {
        if (previousStage.linkedOrConsumed)
            throw new IllegalStateException(MSG_STREAM_LINKED);
        previousStage.linkedOrConsumed = true;
        ...
    }
    

    第一次在源上调用过滤器(第 2 行)时,它会将布尔值设置为 true。由于您不重用过滤器给出的返回值,因此对过滤器的第二次调用(第 3 行)将检测到原始流源(第 1 行)已被链接(由于第一次过滤器调用),因此您的异常得到。

    【讨论】:

      【解决方案4】:

      这是对stream 的滥用,当您将多个.fliter() 附加到它时会检测到该stream

      并不是说它被遍历了不止一次,只是说它“已经被操作过了”

      【讨论】:

      • 我不能在这里说出反对意见,我认为这是至少一个奇怪的错误消息
      • @Eugene 对流的操作发生在调用 filter() 方法时,而不是在终止时。
      猜你喜欢
      • 2012-04-23
      • 2011-06-05
      • 1970-01-01
      • 2013-11-20
      • 2019-10-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多