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