Is there any way to stop a Stream.generate from its Lambda closure?

随声附和 提交于 2020-08-19 04:13:16

问题


I just started playing around Java 8 and Lambda Expression and I am curious if I can stop the Stream generation from inside the Lambda expession by returning a specific value (like null). Is this possible with Stream.generate()?

private int counter;

private void generate()
{
    System.out.println(Stream.generate(() -> {
        if (counter < 10) {
            counter++;
            return RandomUtils.nextInt(100);
        } else {
            return null;
        }
    }).count());
}

Unfortunately this code does not terminate, so by simply returning null will not step out of the stream.


回答1:


This is not possible with Lamdas, you cannot control the flow from inside the expression. Even the API docs says that the Stream.generate generates an infinite stream.

However, you can limit the Stream and achieve the desired functionality simply by using the limit() method:

System.out.println(Stream.generate(() -> RandomUtils.nextInt(100)).limit(10).count());



回答2:


UPDATE (2017):

Java 9 will include this new method:

Stream<T> takeWhile(Predicate<? super T> predicate); 

to limit a stream by condition. So the workaround beneath is not needed anymore.

ORIGINAL:

With Stream.generate this is per definition not possible from a lambda closure. It is by definition endless. Using limit() you are able make your stream fix sized. But this will not help you for conditions like:

if random>10 then stop

There is a possibility to limit a potential endless stream by condition. This is usefull if one does not know the size. Your friend here is a Spliterator and your sample code would look like:

System.out.println( StreamSupport.stream(Spliterators.spliteratorUnknownSize(new Iterator<Integer>() {
    int counter = 0;

    @Override
    public boolean hasNext() {
        return counter < 10;
    }

    @Override
    public Integer next() {
        counter++;
        return RandomUtils.nextInt(100);
    }
}, Spliterator.IMMUTABLE), false).count());

Basically you are able to build a Stream from an Iterator. I am using this construct e.g. for a stream of XMLEvents from Stax XML - parsing.

I know this is not done by lambda constructs but it IHMO solves this lacking feature of stopping the stream item generation by condition.

I would be very interested, if there is a better way to achieve this (I mean this stream construct and not the XML processing ;)) or if there is a fundamental flaw in using streams in this way.




回答3:


// If you are not looking for parallelism, you can use following method:
public static <T> Stream<T> breakStream(Stream<T> stream, Predicate<T> terminate) { 
  final Iterator<T> original = stream.iterator();
  Iterable<T> iter = () -> new Iterator<T>() { 
    T t;
    boolean hasValue = false;

    @Override
    public boolean hasNext() { 
      if (!original.hasNext()) { 
        return false;
      } 
      t = original.next();
      hasValue = true;
      if (terminate.test(t)) { 
        return false;
      } 
      return true;
    } 

    @Override
    public T next() { 
      if (hasValue) { 
        hasValue = false;
        return t;
      } 
      return t;
    } 
  };

  return StreamSupport.stream(iter.spliterator(), false);
}



回答4:


Use StreamSupport.stream(Spliterator, boolean)
See JavaDoc on Spliterator.
Here is example spliterator:

public class GeneratingSpliterator<T> implements Spliterator<T>
{
    private Supplier<T> supplier;
    private Predicate<T> predicate;

    public GeneratingSpliterator(final Supplier<T> newSupplier, final Predicate<T> newPredicate)
    {
        supplier = newSupplier;
        predicate = newPredicate;
    }

    @Override
    public int characteristics()
    {
        return 0;
    }

    @Override
    public long estimateSize()
    {
        return Long.MAX_VALUE;
    }

    @Override
    public boolean tryAdvance(final Consumer<? super T> action)
    {
        T newObject = supplier.get();
        boolean ret = predicate.test(newObject);
        if(ret) action.accept(newObject);
        return ret;
    }

    @Override
    public Spliterator<T> trySplit()
    {
        return null;
    }
}



回答5:


It is possible, you just need to think outside the box.

The following idea is borrowed from Python, the language which introduced me to generator functions...

Just throw an instance of RuntimeException when you are done from within the Supplier<T> closure and catch-and-ignore it at the call site.

An example excerpt (note I have added a safety catch of Stream.limit(Long.MAX_VALUE) to cover the unexpected, though it should never be triggered):

static <T> Stream<T> read(String path, FieldSetMapper<T> fieldSetMapper) throws IOException {
    ClassPathResource resource = new ClassPathResource(path);
    DefaultLineMapper<T> lineMapper = new DefaultLineMapper<>();
    lineMapper.setFieldSetMapper(fieldSetMapper);
    lineMapper.setLineTokenizer(getTokenizer(resource));

    return Stream.generate(new Supplier<T>() {
        FlatFileItemReader<T> itemReader = new FlatFileItemReader<>();
        int line = 1;
        {
            itemReader.setResource(resource);
            itemReader.setLineMapper(lineMapper);
            itemReader.setRecordSeparatorPolicy(new DefaultRecordSeparatorPolicy());
            itemReader.setLinesToSkip(1);
            itemReader.open(new ExecutionContext());
        }

        @Override
        public T get() {
            T item = null;
            ++line;
            try {
                item = itemReader.read();
                if (item == null) {
                    throw new StopIterationException();
                }
            } catch (StopIterationException ex) {
                throw ex;
            } catch (Exception ex) {
                LOG.log(WARNING, ex,
                        () -> format("%s reading line %d of %s", ex.getClass().getSimpleName(), line, resource));
            }
            return item;
        }
    }).limit(Long.MAX_VALUE).filter(Objects::nonNull);
}

static class StopIterationException extends RuntimeException {}

public void init() {
    if (repository.count() == 0) {
        Level logLevel = INFO;
        try {
            read("providers.csv", fields -> new Provider(
                    fields.readString("code"),
                    fields.readString("name"),
                    LocalDate.parse(fields.readString("effectiveStart"), DateTimeFormatter.ISO_LOCAL_DATE),
                    LocalDate.parse(fields.readString("effectiveEnd"), DateTimeFormatter.ISO_LOCAL_DATE)
            )).forEach(repository::save);
        } catch (IOException e) {
            logLevel = WARNING;
            LOG.log(logLevel, "Initialization was interrupted");
        } catch (StopIterationException ignored) {}
        LOG.log(logLevel, "{} providers imported.", repository.count());
    }
}



回答6:


My solution was to generate a null when done and then apply a filter

Stream
 .generate( o -> newObject() )
 .filter( o -> o != null )
 .forEach(...)


来源:https://stackoverflow.com/questions/22630750/is-there-any-way-to-stop-a-stream-generate-from-its-lambda-closure

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!