Hello @[email protected] <[email protected]>,
I am building a simple utility that searches a bunch of files for keywords.
Let's assume that the act of opening a file for reading is very likely to
produce an exception.
Currently, my code looks something like this.
record Result(Path file, String line) {}
Stream<Result> search(final String keyword)
{
return
Files
.walk(Path.of("."))
.filter(Files::isRegularFile)
.flatMap
(
file ->
{
try
{
return
Files
.lines(file)
.filter(line -> line.contains(keyword))
.map(line -> new Result(file, line))
;
}
catch (final Exception exception)
{
throw new IllegalStateException("Failed for " +
file, exception);
}
}
)
;
}
Now, the bug here is obvious from a glance -- that try-catch in flatMap()
doesn't really do what I want. Since Streams are lazy, none of the actual
file opening logic will occur inside the try-catch.
Ok, there are a couple of workarounds here. The most obvious one is just to
execute a terminal operation, like calling Stream::toList followed by
List::stream.
Now this works, but it is not clear that that is the right solution here.
For starters, have I unknowingly opted-out of performance enhancements that
stream could have otherwise given me? I know Streams are lazy, but I don't
know "how lazy" they are. Once the body of the flatMap() lambda is reached,
does it fetch the whole stream contents right away? Does it grab only a few
at a time from each resulting stream, round-robin style? Something else?
I could look at the code to find out, but that only gives me the current
behaviour. And since the documentation for flatMap() doesn't say anything
about it, I am led to believe that the fetching behaviour is unspecified.
It's only specified that it will grab them all, but it is not clear when
and how "eagerly" it would.
Anyways, all of that to say, rather than calling toList() or something
else, wouldn't it be better if I could put the logic of try catch into the
stream itself? Something like being able to wrap whatever exception is
thrown from stream?
This solves the problem of making sure I don't miss out on any features or
performance enhancements provided by the stream library. I can just specify
the behaviour I want -- to wrap the thrown exception in another exception.
That feels very much in the spirit of the Stream API.
Anyways, my idea looks like this.
Files
.lines(file)
.filter(line -> line.contains(keyWord))
.wrapException(Exception.class, (Exception e) -> new
WrapperException("Failed for " + file, e))
;
The signature of wrapException is as follows.
public <EXC1, EXC2> Stream<T> wrapException(Class<EXC> excClass,
Function<EXC1, EXC2> excTransformer)
Now, I know the Stream API is very complex, but I really do think this
carries its weight. We can greatly improve existing error messages while
still keeping in the spirit of the Stream API -- provide the desired
behaviour and let it figure out how to do it.
Please let me know your thoughts.
Thank you for your time and consideration.
David Alayachew