Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is BufferedReader not closed when obtaining `Stream<String>` in try-with-resources?

The reader should be closed when a Stream is used in a try-with-resources.

Given this:

try(Stream<String> lines = new BufferedReader(reader).lines()) {
            return lines.map(it -> trim ? it.trim() : it)
                    .collect(Collectors.toList());
}

... the reader is not being closed??

This test fails:

    AtomicBoolean closed = new AtomicBoolean(false);

    Reader r = new StringReader("  Line1 \n Line2") {

                @Override
                public void close() {
                    super.close();
                    closed.set(true);
                }

            };

    try(Stream<String> lines = new BufferedReader(r).lines()) {
            lines.map(it -> trim ? it.trim() : it)
                    .collect(Collectors.toList());
    }

    assertTrue("Reader was not closed.",closed.get());
like image 742
The Coordinator Avatar asked Dec 20 '22 20:12

The Coordinator


2 Answers

I haven't actually used try-resources syntax. Wish my answer makes sense.

From my understanding, auto-close is closing the resource declared at the statement, nothing else.

Therefore, try(Stream<String> lines = new BufferedReader(r).lines()) { is simply closing lines, but not that buffered reader that have no variable assigned.

If you are intended to close both the buffered reader and the stream (do you really need to close the stream anyway?), iirc, you can have multiple lines in the try statement:

try (BufferedReader br = new BufferedReader(r);
     Stream<String> lines = br.lines()) {
    //....
}

somethings like that. (Haven't tried to compile that, wish it works :P)

like image 153
Adrian Shum Avatar answered Mar 08 '23 23:03

Adrian Shum


Although it is not an immediate answer to your question it ensures that all resources are closed afterwards.

Inspired by Guava's CharSource.lines() implementation that uses a close handler Stream.onClose(Runnable) to close the corresponding BufferedReader when the processed stream was closed and
the characteristic of Stream.flatMap(...) (thanks to this trick)

to call BaseStream.close() after its contents have been placed into this stream.

you could get a stream of lines that will be autoclosed after a terminal operation.

Stream<String> lines = lines(reader);
// ..


Stream<String> lines(Reader reader) {
    BufferedReader br = new BufferedReader(reader);
    Stream<String> lines = br.lines()
            .onClose(() -> {
                try {
                    br.close();
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            });
    return Stream.of(lines).flatMap(s -> s); // Autocloseable exploit of Stream.flatMap()
}
like image 26
bjmi Avatar answered Mar 09 '23 01:03

bjmi