Why effective java third edition recommends try-with-resources instead of try-finally

background

The try-finally statement must be familiar to all java students. Whenever we need to close resources, we will use the try-finally statement. For example, when we use locks, whether it is a local reentrant lock or a Distributed locks will have the following similar structure code, we will unlock in finally to force unlocking:

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

Or when we use java's file stream to read or write files, we will also force the file stream to be closed in finally to prevent resource leaks.

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

In fact, at first glance, there should be no problem with this way of writing, but if we have multiple resources that need to be closed, how should we write it? The most common writing 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();
    }

We define two resources outside, and then close the two resources in sequence in finally. This writing method is like this when I closed the file stream and database connection pool when I first wrote java. Teaching, so what's wrong with this? The problem is that if an exception is thrown during inputStream.close, then outStream.close() will not be executed, which is obviously not the result we want, so it is changed to the following multiple nesting method. Write:

    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();
    }

In this way, even if outStream.close() throws an exception, we will still execute inputStream.close(), because they are in different finally blocks, this does solve our problem, but there are two Issues not resolved:

  • The first question that arises is if we have more than two resources, say ten resources, do we need to write ten nested statements? Can I still see this code after writing it?
  • The second problem is that if an exception occurs in try, and then an exception occurs in finally, it will cause exception coverage, which will cause the exception in finally to overwrite the exception of try.
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();
        }
    }

}
输出结果:Exception in thread "main" java.lang.RuntimeException: close

In the above code, we expect the exception of doSomething to be thrown, but the actual data result is the exception of close, which does not meet our expectations.

try-with-resources

We introduced two problems above, so we introduced the try-with-resources statement in java7. As long as our resources implement AutoCloseablethis interface, we can use this statement. Our previous file stream has implemented this interface, so We can directly use:

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

All our resource definitions are defined in parentheses after try. In this way, we can solve the above problems:

  • First of all, the first question is, in this way, the code is very clean, no matter how many resources you have, you can do it very concisely.
  • For the second exception coverage problem, we can look at it through experiments. 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");
        }
    }

}
输出结果为:
close
close
Exception in thread "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)

We define two CloseTests in the code to verify whether the previous close exception will affect the second one. At the same time, different exceptions are thrown in the close and try blocks. You can see our results and output two closes, Prove that although close throws an exception, both closes are executed. Then output the exception of doSomething, you can find that what we output here is the exception thrown in our try block, and the exception we close is recorded in the exception stack in the way of Suppressed. In this way, we can have both exceptions. record it.

try-with-resources principle

The try-with-resources statement is actually a kind of syntactic sugar. After compilation, it returns to the nested mode we started talking about:

It can be found that after try-with-resources is compiled, it adopts the nested mode, but it is a little different from the previous nesting. When he closes, he uses catch to catch the exception, and then adds it to our real exception. The overall logic is a bit more complicated than our previous nesting.

Summarize

When we close resources, we recommend the use of try-with-resources statement first, but it should be noted here that many resources do not actually implement the AutoCloseable interface. For example, our initial Lock did not implement this interface. At this time, if If we want to use this function, we can encapsulate Lock in a combined way to achieve our purpose.

If you think this article is helpful to you, your attention and forwarding are the greatest support for me, O(∩_∩)O:

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324239794&siteId=291194637