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

2

4

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());
Reinsure answered 2/12, 2013 at 1:18 Comment(2)
Does Stream implement Autocloseble?Adcock
Yes. All Superinterfaces: AutoCloseable, BaseStream<T,Stream<T>>Reinsure
M
10

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)

Mannerly answered 2/12, 2013 at 1:31 Comment(6)
You are right. I looked at the java-8 source and although the Stream is AutoCloseable, there is not close() invoked on the Reader when it finishes.Reinsure
I think that's reasonable too :) Unlike reader/stream decorators that you are decorating a stream, for which closing a decorator should close the underlying stream. Finish using a stream (and closing it) doesn't means the underlying reader is useless and need to be closed. Conceptually we can further work on the reader and get another stream again.Mannerly
@SaintHill I don't think it is reasonable. At least it is not obvious why it is designed this way. I'll raise the question on lambda-dev list.Moulin
@zhong.j.yu Please do and share what you find. I am curious to what the official reason is. Why else would a Stream be AutoCloseable?Reinsure
A stream can holds some underlying resources. Even it is not closing the reader, it still needs to be closed in order to free the resources relates to the stream itself. I don't see anything ambiguous here.Mannerly
@SaintHill the thread is here: mail.openjdk.java.net/pipermail/lambda-dev/2013-December/… I'm not impressed by the explanationMoulin
A
1

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()
}
Aetolia answered 11/3, 2021 at 8:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.