2

Since Java 9 we can use effectively final variables in try-with-resources.

The example below presents a situation where one of the resources initialization throws an exception.

    public static void main(String[] args) {
        Resource1 r1 = new Resource1();
        Resource2 r2 = new Resource2(); // exception will be thrown
        try (r1; r2) {
            System.out.println("TryWithResources.main() try");
        } catch (Exception e) {
            System.out.println("TryWithResources.main() catch");
        }
    }
    
    static class Resource1 implements AutoCloseable {
        @Override
        public void close() throws Exception {
            System.out.println("TryWithResources.Resource1.close()");
        }
    }
    
    static class Resource2 implements AutoCloseable {
        public Resource2() {
            throw new RuntimeException();
        }
        @Override
        public void close() throws Exception {
            System.out.println("TryWithResources.Resource2.close()");
        }
    }

When I run this example, the only output I get is a RuntimeException, meaning that Resource1 was not closed. That was expected, since it wasn't initialized in the try-with-resources.

But, is this the expected outcome, or am I missing anything?

Because, if this is actually the way it is supposed to work, then It seems to me that this new syntax actually removes a lot of the safety that was brought originally by the try-with-resources statement.

Can someone please confirm if that really is the case? And, if positive, why would we ever use this syntax with multiple resources and take that risk?

RinaldoDev
  • 985
  • 6
  • 21

2 Answers2

6

I think you're assuming that the "initialization" occurs in the try statement. The exception is thrown by the constructor before the try-with-resources is reached. In other words, the line try (r1; r2) { in itself does not initialize the resources, it just refers to them as variables. It's different than initializing the resource in try blocks:

try (r1; Resource2 r2 = new Resource2()) { // this causes r1 to close if r2 throws an exception

Having said this, you're right with the point that the new syntax (accessing a final variable) gives flexibility at the expense of a possibility for failing to close previously created resources (as demonstrated in your case). Personally I never had a reason to use that new syntax. There is no good reason I can think of for not creating the resource within the try statement, after all there is no point of using a resource after it is closed.

M A
  • 71,713
  • 13
  • 134
  • 174
  • 1
    Sorry, I think the I wrote the question may have given the impression that I was assuming that. I wasn't. I only wanted to make sure that there wasn't some kind of magic supposed to happen, and that for some reason it wasn't. Your answer clears it, and it's the same conclusion I had. Thanks. :) – RinaldoDev Feb 09 '21 at 19:13
  • @RinaldoDev No worries, in fact after re-reading your question I got the good point you were trying to raise in your question, and that's why I added the second paragraph in my answer. – M A Feb 10 '21 at 08:28
  • 3
    If you want a practical example of a use case for the newer feature, consider [this answer](https://stackoverflow.com/a/32232173/2711488). The method returns a `Stream` backed by JDBC resources. So it can’t use the ordinary `try(…)` as that would unconditionally close the resources before the Stream got returned to the caller. Only in the erroneous case, all resources should be closed safely before delivering the exception to the caller. When successful, a `Runnable` has to be registered at the `Stream` instance to close the already existing resources when the stream got closed by the caller. – Holger Feb 11 '21 at 09:46
  • 1
    The code of that answer uses an `UncheckedCloseable` to represent the already opened resources and handle them uniformly for both cases, closing immediately on error or registering as `Runnable` at the `Stream` in the successful case. Note the `try(Resource dummy = existing)` pattern within that type; that’s the way it has been done before Java 9, now with Java 9+, we can write `try(existing)` which is only relevant for such special cases. In all other cases, the `try(Resource name = creation}expression)` should be used as before. – Holger Feb 11 '21 at 09:50
3

In order to make try-with-resources mechanism work you should put your initialization code into try. Your initialization seems to happen 1 line before. If you would do:

  public static void main(String[] args) {
    try (Resource1 r1 = new Resource1(); Resource2 r2 = new Resource2()) {
      System.out.println("TryWithResources.main() try");
    } catch (Exception e) {
      System.out.println("TryWithResources.main() catch");
    }
  }

that would print:

TryWithResources.Resource1.close()
TryWithResources.main() catch

So your code should be read like: "I want to auto-close r1 and r2 but I prefer to initialize it myself"

And my version should be read like: "I want to auto-close r1 and r2 and also I want to initialize it as a part of try block"

gokareless
  • 1,165
  • 1
  • 10
  • 26