16

Does the stream.spliterator() implicitly closes the stream, or there is a need to explicitly close it afterwards?

Stream<String> stream = Stream.of("a", "b", "c");
Spliterator<T> spliterator = stream.spliterator();
// Some low lever operation with the spliterator
stream.close(); // do we need to close?

At first glance, it seems that the .spliterator() method closes the stream, but without calling stream.close(). At least if I close it straight away after the .spliterator() method is invoked, it seems not affection the spliterator operations.

Stream<String> stream = Stream.of("a", "b", "c").limit(2);
Spliterator<T> spliterator = stream.spliterator();
stream.close();
// Some low lever operation with the spliterator

This question can be extended to other stream methods, for example, the .findAny().

stream.findAny() // Can I assume that I don't need to close the stream?
stream.onClose(() -> System.out.println("hi!")).findAny()`
// when the `onClose()` action will be called?

The reason for that question is to have deep clarity when a stream needs explicitly to be closed, and in the cases where I don't need to explicitly close it, when the onClose() defined actions will take place?

Naman
  • 27,789
  • 26
  • 218
  • 353
Tet
  • 1,155
  • 10
  • 14
  • Stream interface extends the AutoCloseable interface, so I guest the `onClose` actions will be called sometime when the garbage collector disposes the stream. However it's best practice to close it as soon as possible. – Tet Oct 11 '17 at 13:10
  • 4
    no, there is no garbage collector driven closing of the stream—and the results would be disastrous, if stream had such a finalizer. The underlying resource itself *may* have an automatic cleanup, but that depends on the resource. But that does not imply the execution of any close handler registered to the stream. – Holger Oct 11 '17 at 13:23
  • 2
    Just to add to details, here is a good insight over streams and [TWR by Stuart](https://stackoverflow.com/a/22929990/1746118) – Naman Oct 11 '17 at 13:48
  • 3
    Thanks @nullpointer, the argument of who is responsible to handle the exception in case of `exception` during the `close()` method make sense. – Tet Oct 11 '17 at 14:06
  • Possible duplicate of [Why doesn't Java close() stream after a terminal operation is issued?](https://stackoverflow.com/questions/28813637/why-doesnt-java-close-stream-after-a-terminal-operation-is-issued) – Vadzim May 27 '19 at 22:35

4 Answers4

11

Terminal operations never close the stream. Closing has to be done manually. The only place where automatic closing happens is within the flatMap operation, where manual closing of the substreams usually created on-the-fly would be somewhere between hard and impossible.

This also applies to the Stream.spliterator() method. In your examples, it makes no difference because the streams created via Stream.of(…) do not need to be closed and have no onClose() operation registered by default.

You have to consult the documentation of the factory methods to find out when a stream need to be closed. E.g. like Files#lines(Path, Charset).

See also Does collect operation on Stream close the stream and underlying resources? or Does Java 8 Stream.iterator() auto-close the stream when it's done?

Gray
  • 115,027
  • 24
  • 293
  • 354
Holger
  • 285,553
  • 42
  • 434
  • 765
  • Thanks @Holger, just for information, if a terminal operation leaves the stream in a unusable state (e.g. `.findAny()`), why does not the terminal operation close the stream itself before returning the value? – Tet Oct 11 '17 at 13:39
  • 2
    @Tet: it follows the principle that the responsibility of closing lies at the creator of the resource and the terminal operation did not allocate the resource. Likewise, an `InputStream` doesn’t close itself when you read the last byte. – Holger Oct 11 '17 at 13:44
5

Nothing changed concerning the closing of Streams in Java 9. You still need to manually do it if the underlying resource should be freed. You should never rely on the garbage collector to do it. The docs still say:

Streams have a BaseStream.close() method and implement AutoCloseable. Operating on a stream after it has been closed will throw IllegalStateException. Most stream instances do not actually need to be closed after use, as they are backed by collections, arrays, or generating functions, which require no special resource management. Generally, only streams whose source is an IO channel, such as those returned by Files.lines(Path), will require closing. If a stream does require closing, it must be opened as a resource within a try-with-resources statement or similar control structure to ensure that it is closed promptly after its operations have completed.

M A
  • 71,713
  • 13
  • 134
  • 174
4

The call to spliterator() method returns a Spliterator for the elements of this stream and its a terminal operation.

To answer your question - No, the spliterator method or for that sake none of the other terminal operations also does not close the stream.

This stands documented for terminal operations as -

After the terminal operation is performed, the stream pipeline is considered consumed, and can no longer be used.... In almost all cases, terminal operations are eager, completing their traversal of the data source and processing of the pipeline before returning. Only the terminal operations iterator() and spliterator() are not; these are provided as an "escape hatch" to enable arbitrary client-controlled pipeline traversals in the event that the existing operations are not sufficient to the task.

Over closing the Stream on another hand the docs state that:-

Most stream instances do not actually need to be closed after use, as they are backed by collections, arrays, or generating functions, which require no special resource management. Generally, only streams whose source is an IO channel, such as those returned by Files.lines(Path), will require closing.


The AutoCloseable states to match to it-

It is possible, and in fact common, for a base class to implement AutoCloseable even though not all of its subclasses or instances will hold releasable resources.

which is how the BaseStream extends it and the close() doesn't impact far more than the streams over the ones using the resources such as Files.lines(...).

However, when using facilities such as Stream that support both I/O-based and non-I/O-based forms, try-with-resources blocks are in general unnecessary when using non-I/O-based forms.

Naman
  • 27,789
  • 26
  • 218
  • 353
0

The method stream.spliterator() does not close the Stream, like no other terminal operation. You may or may not know, if you need to close the Stream, that's still a big debate. I personally would like to have all terminal operations except spliterator() and iterator() to implicitly close the Stream for various reasons. There is no harm in doing so, since most implementations would simply do nothing.

You can find one implementation in the java.util.stream.AbstractPipeline. The close action that has been added when calling stream.onClose(action) will be called in the moment stream.close() is called.

I cannot tell you when a Stream explicitly needs to be closed, since the current policy is to document it at the method that returns the Stream, like Files.list(Path):

The returned stream contains a reference to an open directory. The directory is closed by closing the stream. ...

This method must be used within a try-with-resources statement or similar control structure to ensure that the stream's open directory is closed promptly after the stream's operations have completed.

When you call stream.spliterator(), the returned Spliterator may be lazy (see also AbstractPipeline.lazySpliterator(..)), thus still needs to operate on the elements of the original Stream. If you close the Stream before traversing the elements of the Spliterator, you might get an exception or the Spliterator simply does not operate anymore.

public static void main(final String[] args) throws IOException {
    try (final Stream<Path> stream = Files.list(Paths.get(""))) {
        stream.onClose(() -> System.out.println("stream closed"));
        final Spliterator<Path> split = stream.spliterator();

        /*
         * uncomment this to see what happens when you close the
         * stream before you traverse the spliterator
         */
        // stream.close();

        split.forEachRemaining(System.out::println);
    }
}

The problem is now, if you want to have a method that returns a Spliterator, the caller has no close() method that can be called on the Spliterator.

benez
  • 1,856
  • 22
  • 28