Arbitrary finite streams in Java
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