RFR: 8274412: Add a method to Stream API to consume and close the stream without using try-with-resources
Peter Levart
peter.levart at gmail.com
Sat Nov 6 10:08:23 UTC 2021
Hi,
On 11/10/2021 20:42, John Rose wrote:
> To summarize: We can (and should) try to model “close-debt”
> using interfaces. Doing so opens up the usual cans of worms
> with interoperability and exceptions, but still gives us a
> model we can contemplate. We can (and should) contemplate
> how such a model would give us leverage for further syntax
> sugar and/or combinatorial API points for handling close-debt
> at various points in the language and our APIs.
This RFR is closed, but inspired by John's discussion and fueled by
grief that I have each time when I try to combine Stream processing with
resources that throw checked exceptions on construction and destruction
(usually same kind), I created some helper classes/interfaces that might
make such attempts easier and I'd like to present them here to
demonstrate what is achievable with API and current syntax features...
Let's start with a code snippet that shows how this must be done without
such helper classes today. A method that takes a path and a regular
expression and returns a list of lines read from files found recursively
below given path that contain at least one match of the regular expression:
```
public static List<String> grep(Path dir, Pattern pattern) {
try (
var paths = Files.find(dir, 100, (p, a) -> true)
) {
return
paths
.filter(f -> Files.isRegularFile(f) &&
Files.isReadable(f))
.flatMap(
f -> {
BufferedReader br;
try {
br = Files.newBufferedReader(f,
StandardCharsets.UTF_8);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
return br.lines().onClose(() -> {
try {
br.close();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
)
.filter(line -> pattern.matcher(line).find())
.collect(Collectors.toList());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
```
This can be rewritten in a more functional style using helpers to:
```
public static final Try<BaseStream<?, ?>, IOException> streamTry =
new Try<>(BaseStream::close, UncheckedIOException::new);
public static final Try<Closeable, IOException> ioTry =
new Try<>(Closeable::close, UncheckedIOException::new);
public static List<String> grep(Path dir, Pattern pattern) {
return streamTry
.with(
() -> Files.find(dir, 100, (p, a) -> true)
)
.applyAndDispose(
paths -> paths
.filter(f -> Files.isRegularFile(f) &&
Files.isReadable(f))
.flatMap(
f -> ioTry.with(() ->
Files.newBufferedReader(f, StandardCharsets.UTF_8))
.apply((br, dispose) ->
br.lines().onClose(dispose))
)
.filter(line -> pattern.matcher(line).find())
.collect(Collectors.toList())
);
}
```
Note that this helper is not limited to `AutoCloseable` resources. It
captures resource destruction function and checked exception wrapping
function in an instance of `Try` which provides two styles of handling
resource lifecycle:
`with(constructor).applyAndDispose(consumptor)` - construction,
consumprion with automatic resource disposal after the `consumptor`
function terminates but before `applyAndDispose` terminates
`with(constructor).apply(consumptorAndDestructor)` - construction,
consumption with arranged resource disposal by arranging to call the
passed-in `Runnable` instance in the `consumptorAndDestructor` function
itself.
Both styles are demonstrated above.
Here's the `Try` helper:
https://gist.github.com/plevart/c26e9908573d4a28c709b7218b001ea8
Regards, Peter
More information about the core-libs-dev
mailing list