Java makes it easy to generate arbitrary infinite Streams, but what about finite ones ?

The standard api provide a few ways to generate streams from scratch, like using a Stream::builder, Stream::of, Stream::iterate and Stream::generate. Of these only the latter lets the programmer generate a stream that is completely arbitrary and lazily (i.e. without having to provide all the values beforehand, which kind of defeats the point of using streams).

I found strange, at first, that Stream::generate only let you generate infinite streams. The following is a Stream of square values of positive integers (yes I know, there are better ways to do this, but it is just an example), Btw, the trick with the array of length 1 is to work around limitation about final values inside the lambda.

int x[] = new int[1];
Stream<Integer> s = Stream.generate(() -> {
    int result = x[0]*x[0];
    x[0]+=1;
    return result;
});

What if I want to stop the generation after a while ? For example, once the value is greater than 1000 ? For some reasons the answer to this was escaping me, but it is quite obvious: you generate an infinite stream, but you apply a filter afterwards:

int x[] = new int[1];
Stream<Integer> s = Stream.generate(() -> {
    int result = x[0]*x[0];
    x[0]+=1;
    return result;
}).takeWhile( n -> n < 1000 );

The issue with this approach is that it requires the ability to generate more than needed, which is something you don’t always can do, expecially where Stream::generate is actually useful. Let’s say for example that you want to generate all the sums a+b where both a and b are greater or equal than 0 and less than 100. You don’t really have a rule that you can apply afterwards. In such cases, we can generate a stream of Optional, and stop as soon as we get an empty one.

int x[] = new int[1];
int y[] = new int[1];
Stream<Optional<Integer>> so = Stream.generate(() -> {
    if (x[0] >= 100 && y[0] >= 100) {
        return Optional.empty();
    }
    int result = x[0] + y[0];
    x[0] += 1;
    if (x[0] >= 100) {
        x[0] = 0;
        y[0] += 1;
    }
    return Optional.of(result);
});
Stream<Integer> s = so
        .takeWhile(Optional::isPresent)
        .map(Optional::get);

This approach is completely generic and can be applied almost everywhere