-
CSR
-
Resolution: Unresolved
-
P4
-
None
-
None
-
minimal
-
-
Java API
-
JDK
Summary
It's proposed to add new default method named consumeAndClose
to the following interfaces:
- java.util.stream.Stream
- java.util.stream.IntStream
- java.util.stream.LongStream
- java.util.stream.DoubleStream
Problem
Currently, when the stream holds a resource, it's necessary to wrap it with try-with-resources. This undermines the compact and fluent style of stream API calls. For example, if we want to get the List
of files inside the directory and timely close the underlying filehandle, we should use something like this:
List<Path> paths;
try (Stream<Path> stream = Files.list(Path.of("/etc"))) {
paths = stream.toList();
}
// use paths
Try-with-resources construct is heavy and doesn't align well with the usual fluent Stream API style. As a result, people often forget to close the stream that holds a resource and needs to be closed. This may cause file descriptors to leak.
Solution
A proposed solution: add the consumeAndClose
method to the stream interfaces. This method accepts a function that accepts a stream and performs a terminal stream operation returning the result. The function is executed and the original stream is closed after that (even if the function execution failed). Having this method, the code snippet above could be rewritten as:
List<Path> list = Files.list(Path.of("/etc")).consumeAndClose(Stream::toList);
Now, the stream could be processed as a single expression which is more in the Stream API spirit.
Alternative solutions:
Instead of adding four methods to all the stream specializations, it's possible to add only one method to the super-interface
java.util.stream.BaseStream
. However, Brian Goetz was against this.It's possible to avoid changes in the Stream interface and provide overloads for methods producing the stream that holds the resource. For example, we may accompany
Files.list(Path dir)
withFile list(Path dir, Function<? super Stream<Path>, ? extends T> function)
overload. The advantage of this approach is that overloads are easier to discover than completely new method. Also, here it's possible to add exception translation (e.g., unwrapUncheckedIOException
, so only checkedIOException
is thrown in case of I/O error). The disadvantage is that it requires adding one method per every existing method that holds a resource which could be too many methods. Sometimes, methods already have overloads. E.g., there isFiles.lines(Path)
andFiles.lines(Path, Charset)
, so we will need two more overloads making the API somewhat bloated and confusing. Also, some existing methods use variable arity parameter (e.g.,Files.walk(Path, FileVisitOption...)
), so it will be not possible to put a new lambda argument at the end which is again confusing.It's possible to revisit the old decision and automatically close the consumed streams after the terminal operation is executed. However, this won't work with iterator() and spliterator() terminal operations, as they still need the resource to be opened and there's no way to indicate that iterator() is not necessary anymore (it could be legally dropped in the middle of traversal). Also, this introduces quite a major behavioral change. For background, here's a StackOverflow discussion about this topic.
Specification
The following are the added methods, along with the proposed specification.
// java.util.stream.Stream
/**
* Applies given function to this stream, then closes the stream.
* No further operation on the stream will be possible after that.
*
* @apiNote
* This method allows consuming and closing the stream in the single
* operation, making it possible to perform the operation on the stream
* holding a resource in a single statement. For example, it's possible to write:
*
* <pre>{@code
* List<Path> paths = Files.list(Path.of(".")).consumeAndClose(Stream::toList);
* }</pre>
*
* This is equivalent to:
*
* <pre>{@code
* try (Stream<Path> stream = Files.list(Path.of("."))) {
* List<Path> paths = stream.consumeAndClose(Stream::toList);
* }}</pre>
*
* @param function function to apply
* @param <R> type of the function result
* @return result of the function
* @throws NullPointerException if the supplied function is null
* @see #close()
* @since 18
*/
default <R> R consumeAndClose(Function<? super Stream<T>, ? extends R> function) { ... }
// java.util.stream.IntStream
/**
* Applies given function to this stream, then closes the stream.
* No further operation on the stream will be possible after that.
*
* @apiNote
* This method allows consuming and closing the stream in the single
* operation, making it possible to perform the operation on the stream
* holding a resource in a single statement.
*
* @param function function to apply
* @param <R> type of the function result
* @return result of the function
* @throws NullPointerException if the supplied function is null
* @see #close()
* @since 18
*/
default <R> R consumeAndClose(Function<? super IntStream, ? extends R> function) { ... }
// java.util.stream.LongStream
/**
* Applies given function to this stream, then closes the stream.
* No further operation on the stream will be possible after that.
*
* @apiNote
* This method allows consuming and closing the stream in the single
* operation, making it possible to perform the operation on the stream
* holding a resource in a single statement.
*
* @param function function to apply
* @param <R> type of the function result
* @return result of the function
* @throws NullPointerException if the supplied function is null
* @see #close()
* @since 18
*/
default <R> R consumeAndClose(Function<? super LongStream, ? extends R> function) { ... }
// java.util.stream.DoubleStream
/**
* Applies given function to this stream, then closes the stream.
* No further operation on the stream will be possible after that.
*
* @apiNote
* This method allows consuming and closing the stream in the single
* operation, making it possible to perform the operation on the stream
* holding a resource in a single statement.
*
* @param function function to apply
* @param <R> type of the function result
* @return result of the function
* @throws NullPointerException if the supplied function is null
* @see #close()
* @since 18
*/
default <R> R consumeAndClose(Function<? super DoubleStream, ? extends R> function) { ... }
- csr of
-
JDK-8274412 Add a method to Stream API to consume and close the stream without using try-with-resources
-
- Open
-
- relates to
-
JDK-8318856 java.util.stream.Stream should close closeable spliterator
-
- New
-