background

We use the try-finally statement whenever we need to close a resource. For example, when we use a lock, either a locally reentrant lock or a distributed lock, we use the following structure: We use unlock in finally to force the unlock:

    Lock lock = new ReentrantLock();
    lock.lock();
    try{
        // doSometing
    }finally {
        lock.unlock();
    }
Copy the code

Or when we use Java file streams to read or write files, we also force the file streams to close in finally to prevent resource leaks.

    InputStream inputStream = new FileInputStream("file");
    try {
        System.out.println(inputStream.read(new byte[4]));
    }finally {
        inputStream.close();
    }
Copy the code

At first glance, this should be fine, but what if we have multiple resources that need to be closed? The most common way to write this is as follows:

InputStream inputStream = new FileInputStream("file");
    OutputStream outStream = new FileOutputStream("file1");

    try {
        System.out.println(inputStream.read(new byte[4]));
        outStream.write(new byte[4]);
    }finally {
        inputStream.close();
        outStream.close();
    }
Copy the code

We define two resources on the outside, and then close them in sequence in finally. This is how I used to close file streams and database connection pools when I first started writing Java. The problem is that if an exception is thrown on inputstream.close, outstream.close () will not be executed.

    InputStream inputStream = new FileInputStream("file");
    try {
        System.out.println(inputStream.read(new byte[4]));
        try{
            OutputStream outStream = new FileOutputStream("file1");
            outStream.write(new byte[4]);
        }finally {
            outStream.close();
        }
    }finally {
        inputStream.close();
    }
Copy the code

In this way, even if outstream.close () throws an exception, we still execute inputStream.close() because they are ina different finally block. This does solve our problem, but there are two problems left:

  • The first question that comes up is if we have more than two resources, say ten resources, do we need to write ten nested statements? Can I still look at this code after I write it?
  • The second problem is that if we get an exception ina try, and then an exception in finally, we will overwrite the exception in the finally.
public class CloseTest { public void close(){ throw new RuntimeException("close"); } public static void main(String[] args) { CloseTest closeTest = new CloseTest(); try{ throw new RuntimeException("doSomething"); }finally { closeTest.close(); }}} output: the Exception in the thread "is the main" Java. Lang. RuntimeException: closeCopy the code

In the code above, we expected the exception to throw doSomething, but the actual data turned out to be a close exception, which is not what we expected.

try-with-resources

We introduced the try-with-resources statement in Java7, which allows us to use this statement as long as our resources implement the AutoCloseable interface. Our file streams already implement this interface, so we can use it directly:

try(InputStream inputStream = new FileInputStream("file");
            OutputStream outStream = new FileOutputStream("file1")) {
            System.out.println(inputStream.read(new byte[4]));
            outStream.write(new byte[4]);
        }
Copy the code

All of our resource definitions are defined in parentheses after the try, so we can solve some of the problems mentioned above:

  • First question, the way we do this, the code is very clean, no matter how many resources you have, you can do it very simply.
  • For the second exception coverage problem, we can see it experimentally. We rewrite the code as follows:
public class CloseTest implements AutoCloseable { @Override public void close(){ System.out.println("close"); throw new RuntimeException("close"); } public static void main(String[] args) { try(CloseTest closeTest = new CloseTest(); CloseTest closeTest1 = new CloseTest();) { throw new RuntimeException("Something"); }}} the output is: close close the Exception in the thread "is the main" Java. Lang. RuntimeException: Something at fudao.CloseTest.main(CloseTest.java:33) Suppressed: java.lang.RuntimeException: close at fudao.CloseTest.close(CloseTest.java:26) at fudao.CloseTest.main(CloseTest.java:34) Suppressed: java.lang.RuntimeException: close at fudao.CloseTest.close(CloseTest.java:26) at fudao.CloseTest.main(CloseTest.java:34)Copy the code

We define two closetests in the code to verify whether the exception from close will affect the second one. We also throw different exceptions in the close and try blocks. We then output an exception for doSomething, and you can see that what we’re output here is the exception that was thrown from our try block, and our closed exception is Suppressed to the exception stack, so we can record both of them this way.

The try – with – resources principle

The try-with-resources statement is a syntactic candy that, when compiled, returns to the nested pattern we started with:

As you can see, try-with-resources are compiled and nested again, but it is a bit different from the previous nesting. It uses a catch to catch the exception when it is closed, and then adds it to the actual exception. The overall logic is a bit more complicated than our previous nesting.

conclusion

When closing resources, we recommend try-with-resources as a priority, but note that many resources do not implement the AutoCloseable interface. For example, our original Lock did not implement this interface. Lock can be encapsulated in a composite manner to achieve our purpose.

If you find this article helpful to you, your attention and forwarding will be my biggest support.