> At that point, the real question is why not call close() at the end of all 
> terminal operations by wrapping each implementation in a try-with-resources ?

Now, it will be incompatible change. Also, this won't work with
iterator() and spliterator() terminal operations. I believe it's best
described by Brian in this SO answer:
https://stackoverflow.com/a/34073306/4856258

Quoted here, for convenience:

> Yes, this was a deliberate decision. We considered both alternatives.
>
> The operating design principle here is "whoever acquires the resource should 
> release the resource". Files don't auto-close when you read to EOF; we expect 
> files to be closed explicitly by whoever opened them. Streams that are backed 
> by IO resources are the same.
>
> Fortunately, the language provides a mechanism for automating this for you: 
> try-with-resources. Because Stream implements AutoCloseable, you can do:
>
> try (Stream<String> s = Files.lines(...)) {
>    s.forEach(...);
> }
>
> The argument that "it would be really convenient to auto-close so I could 
> write it as a one-liner" is nice, but would mostly be the tail wagging the 
> dog. If you opened a file or other resource, you should also be prepared to 
> close it. Effective and consistent resource management trumps "I want to 
> write this in one line", and we chose not to distort the design just to 
> preserve the one-line-ness.

With best regards,
Tagir Valeev.

>
> RĂ©mi
>
> ----- Original Message -----
> > From: "Brian Goetz" <brian.go...@oracle.com>
> > To: "Tagir Valeev" <amae...@gmail.com>, "core-libs-dev" 
> > <core-libs-dev@openjdk.java.net>
> > Sent: Lundi 27 Septembre 2021 22:41:00
> > Subject: Re: Discussion: easier Stream closing
>
> > In Java 8, I think we were reluctant to lean on the idiom of "pass me a
> > lambda and I'll pass it the confined data"), because Java developers
> > were already struggling to understand lambdas.  But now that we're
> > mostly over that hurdle, API points that accept Consumer<Something> are
> > a powerful way to gain confinement (as we've seen in StackWalker, and
> > also in the explorations of ScopeLocal in Loom.)  So I have no objection
> > to this idiom.
> >
> > I have some concern that putting these side-by-side with existing
> > Files.walk(...) methods will be a source of confusion, creating two
> > different ways to do the same thing.
> >
> > I would rather not add anything new to BaseStream; it was a mistake to
> > expose at all, and I'd rather see it deprecated than add more to it.
> > However, adding it individually to the Stream implementations is
> > equivalent, and doesn't have this problem.
> >
> > The consumeAndClose approach is clever, in that it adds one API point
> > that works for all streams, rather than having to add a new API point
> > for every factory of a closeable stream; on the other hand, it is
> > dramatically less discoverable, and so requires more education to get
> > people to use it (and, as you say, has the exception problem.)
> >
> > On 9/26/2021 5:27 AM, Tagir Valeev wrote:
> >> Hello!
> >>
> >> With current NIO file API, a very simple problem to get the list of
> >> all files in the directory requires some ceremony:
> >>
> >> List<Path> paths;
> >> try (Stream<Path> stream = Files.list(Path.of("/etc"))) {
> >>      paths = stream.toList();
> >> }
> >>
> >> If we skip try-with-resources, we may experience OS file handles leak,
> >> so it's desired to keep it. Yet, it requires doing boring stuff. In
> >> this sense, classic new File("/etc").list() was somewhat more
> >> convenient (despite its awful error handling).
> >>
> >> I like how this problem is solved in StackWalker API [1]: it limits
> >> the lifetime of the Stream by requiring a user-specified function to
> >> consume it. After the function is applied, the stream is closed
> >> automatically. We could add a similar overload to the Files API. As an
> >> additional feature, we could also translate all UncheckedIOException's
> >> back to IOException for more uniform exception processing:
> >>
> >> /**
> >>   * @param dir  The path to the directory
> >>   * @param function  function to apply to the stream of directory files
> >>   * @param <T> type of the result
> >>   * @return result of the function
> >>   * @throws IOException if an I/O error occurs when opening the directory, 
> >> or
> >>   * UncheckedIOException is thrown by the supplied function.
> >>   */
> >> public static <T> T list(Path dir, Function<? super Stream<Path>, ?
> >> extends T> function) throws IOException {
> >>      try (Stream<Path> stream = Files.list(dir)) {
> >>          return function.apply(stream);
> >>      }
> >>      catch (UncheckedIOException exception) {
> >>          throw exception.getCause();
> >>      }
> >> }
> >>
> >> In this case, getting the List of all files in the directory will be
> >> as simple as
> >>
> >> List<Path> paths = Files.list(Path.of("/etc"), Stream::toList);
> >> This doesn't limit the flexibility. For example, if we need only file
> >> names instead of full paths, we can do this:
> >> List<Path> paths = Files.list(Path.of("/etc"), s ->
> >> s.map(Path::getFileName).toList());
> >>
> >> Alternatively, we could enhance the BaseStream interface in a similar
> >> way. It won't allow us to translate exceptions, but could be useful
> >> for every stream that must be closed after consumption:
> >>
> >> // in BaseStream:
> >>
> >> /**
> >>   * Apply a given function to this stream, then close the stream.
> >>   * No further operation on the stream will be possible after that.
> >>   *
> >>   * @param function function to apply
> >>   * @param <R> type of the function result
> >>   * @return result of the function
> >>   * @see #close()
> >>   */
> >> default <R> R consumeAndClose(Function<? super S, ? extends R> function) {
> >>      try(@SuppressWarnings("unchecked") S s = (S) this) {
> >>          return function.apply(s);
> >>      }
> >> }
> >>
> >> The usage would be a little bit longer but still more pleasant than
> >> explicit try-with-resources:
> >>
> >> List<Path> list = 
> >> Files.list(Path.of("/etc")).consumeAndClose(Stream::toList);
> >>
> >> On the other hand, in this case, we are free to put intermediate
> >> operations outside of consumeAndClose, so we won't need to nest
> >> everything inside the function. Only terminal operation should be
> >> placed inside the consumeAndClose. E.g., if we need file names only,
> >> like above, we can do this:
> >>
> >> List<Path> list =
> >> Files.list(Path.of("/etc")).map(Path::getFileName).consumeAndClose(Stream::toList);
> >>
> >> What do you think?
> >>
> >>
> >> [1]
> > > https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/StackWalker.html#walk(java.util.function.Function)

Reply via email to