How do streams stop?

前端 未结 2 613
星月不相逢
星月不相逢 2020-12-06 02:33

I was wondering when I created my own infinite stream with Stream.generate how the Streams which are in the standard library stop...

For example when yo

相关标签:
2条回答
  • 2020-12-06 03:01

    Finite streams simply aren’t created via Stream.generate.

    The standard way of implementing a stream, is to implement a Spliterator, sometimes using the Iterator detour. In either case, the implementation has a way to report an end, e.g. when Spliterator.tryAdvance returns false or its forEachRemaining method just returns, or in case of an Iterator source, when hasNext() returns false.

    A Spliterator may even report the expected number of elements before the processing begins.

    Streams, created via one of the factory methods inside the Stream interface, like Stream.generate may be implemented either, by a Spliterator as well or using internal features of the stream implementation, but regardless of how they are implemented, you don’t get hands on this implementation to change their behavior, so the only way to make such a stream finite, is to chain a limit operation to the stream.

    If you want to create a non-empty finite stream that is not backed by an array or collection and none of the existing stream sources fits, you have to implement your own Spliterator and create a stream out of it. As told above, you can use an existing method to create a Spliterator out of an Iterator, but you should resists the temptation to use an Iterator just because it’s familiar. A Spliterator is not hard to implement:

    /** like {@code Stream.generate}, but with an intrinsic limit */
    static <T> Stream<T> generate(Supplier<T> s, long count) {
        return StreamSupport.stream(
                   new Spliterators.AbstractSpliterator<T>(count, Spliterator.SIZED) {
            long remaining=count;
    
            public boolean tryAdvance(Consumer<? super T> action) {
                if(remaining<=0) return false;
                remaining--;
                action.accept(s.get());
                return true;
            }
        }, false);
    }
    

    From this starting point, you can add overrides for the default methods of the Spliterator interface, weighting development expense and potential performance improvements, e.g.

    static <T> Stream<T> generate(Supplier<T> s, long count) {
        return StreamSupport.stream(
                   new Spliterators.AbstractSpliterator<T>(count, Spliterator.SIZED) {
            long remaining=count;
    
            public boolean tryAdvance(Consumer<? super T> action) {
                if(remaining<=0) return false;
                remaining--;
                action.accept(s.get());
                return true;
            }
    
            /** May improve the performance of most non-short-circuiting operations */
            @Override
            public void forEachRemaining(Consumer<? super T> action) {
                long toGo=remaining;
                remaining=0;
                for(; toGo>0; toGo--) action.accept(s.get());
            }
        }, false);
    }
    
    0 讨论(0)
  • 2020-12-06 03:16

    I have created a generic workaround for this

    public class GuardedSpliterator<T> implements Spliterator<T> {
    
      final Supplier<? extends T> generator;
    
      final Predicate<T> termination;
    
      final boolean inclusive;
    
      public GuardedSpliterator(Supplier<? extends T> generator, Predicate<T> termination, boolean inclusive) {
        this.generator = generator;
        this.termination = termination;
        this.inclusive = inclusive;
      }
    
      @Override
      public boolean tryAdvance(Consumer<? super T> action) {
        T next = generator.get(); 
        boolean end = termination.test(next);
        if (inclusive || !end) {
          action.accept(next);
        }
        return !end;
      }
    
      @Override
      public Spliterator<T> trySplit() {
        throw new UnsupportedOperationException("Not supported yet.");
      }
    
      @Override
      public long estimateSize() {
        throw new UnsupportedOperationException("Not supported yet.");
      }
    
      @Override
      public int characteristics() {
        return Spliterator.ORDERED;
      }
    
    }
    

    Usage is pretty easy:

    GuardedSpliterator<Integer> source = new GuardedSpliterator<>(
        ()  -> rnd.nextInt(),
        (i) -> i > 10,
        true
    );
    
    Stream<Integer> ints = StreamSupport.stream(source, false);
    
    ints.forEach(i -> System.out.println(i));    
    
    0 讨论(0)
提交回复
热议问题