· Zen HuiFer · Learn  · 需要6 分钟阅读

Why is it recommended to prioritize using try with resources instead of try finally

Learn why try-with-resources is recommended over try-finally for handling resource management in Java. It automatically closes resources, reducing boilerplate code and risk of leaks.

Learn why try-with-resources is recommended over try-finally for handling resource management in Java. It automatically closes resources, reducing boilerplate code and risk of leaks.

Why is it recommended to prioritize using try with resources instead of try finally

1、 Background introduction

try-with-resourcesIt is a new exception handling mechanism introduced in JDK 7, It allows developers to avoid explicit releasetry-catchResources used in statement blocks

For example, we take file resource copying as an example, which is familiar to everyonetry-catch-finallyWrite it as follows:

public class ResourceTest1 {    public static void main(String[] args) {
        BufferedInputStream bin = null;
        BufferedOutputStream bout = null;
        try {
            bin = new BufferedInputStream(new FileInputStream(new File( "test.txt")));
            bout = new BufferedOutputStream(new FileOutputStream(new File( "out.txt")));
            int b;
            while ((b = bin.read()) != -1) {
                bout.write(b);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
              //Close file stream     
            if (bin != null) {
                try {
                    bin.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bout != null) {
                try {
                    bout.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

We will now change it to usetry-with-resourcesYou will be surprised to find that with just a few simple lines of code, programming can be done without explicitly closing resources. Here’s how to do it:

public class ResourceTest2 {    public static void main(String[] args) {
        try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
             BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) {
            int b;
            while ((b = bin.read()) != -1) {
                bout.write(b);
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Before JDK7, developers had to keep in mind when dealing with resources that had to be closedtry-catchUsed in statementsfinallyImplement the method of closing resources, otherwise as the program continues to run, resource leakage will accumulate into a major production accident. If multiple resources are opened simultaneously in your program, you will be surprised to find that there are more code to close resources than business code, making it even more difficult to read and manage the code clearly.

Therefore, in this context,try-with-resourcesThus born, its original design intention was to alleviate the release of developerstryThe resource burden used in the block.

used to ittry-catch-finallyClassmates who write may ask if all operations involving resources can be usedtry-with-resourcesProgramming? Are there any pitfalls in using this programming method? If there are pits, where should we pay attention when using them

Okay, without further ado, let’s take a look together todaytry-with-resourcesProgramming principles.

2、 Practical Explanation

try-with-resourcesThe statement ensures that each resource is closed at the end of the statement, But there is a prerequisite, which is that this resource must be implementedjava.lang.AutoCloseableThe interface can only be executed and closed

try-with-resourcesIn programming mode, the prerequisite for developers not to explicitly close resources is that the resource must be implementedjava.lang.AutoCloseableInterface and rewritecloseMethod, otherwise it cannot be usedtry-with-resourcesDeclare variables in the middle.

For example, we can close a single resource as follows:

public class TryResourceDemo implements AutoCloseable {    public void doSomething(){
        System.out.println("do something");
    }    @Override
    public void close() throws Exception {
        System.out.println("resource is closed");
    }
}
public class TryResourceTest {    public static void main(String[] args) {
        try(TryResourceDemo res = new TryResourceDemo()) {
            res.doSomething();
        } catch(Exception ex) {
            ex.printStackTrace();
        }
    }
}

The running result is as follows:

do something
resource is closed

It can be clearly seen that,closeThe method has been called!

Next, we will open the decompiled version againTryResourceTest.classYou will be surprised to find that the compiler automatically adds code to the file codefinallyMethod, and will callcloseMethod, close the resource!

public class TryResourceTest {    public static void main(String[] args) {
        try {
            TryResourceDemo res = new TryResourceDemo();
            Throwable var2 = null;            try {
                res.doSomething();
            } catch (Throwable var12) {
                var2 = var12;
                throw var12;
            } finally {
                if (res != null) {
                    if (var2 != null) {
                        try {
                            res.close();
                        } catch (Throwable var11) {
                            var2.addSuppressed(var11);
                        }
                    } else {
                        res.close();
                    }
                }            }
        } catch (Exception var14) {
            var14.printStackTrace();
        }    }
}

That is to say, usingtry-with-resourcesProgramming is actually an explicit addition of code by the compilerfinallyMethod, eliminating the need for developers to manually close resources!

3、 Order of resource closure

We only introduced the scenario of closing a single resource above. If there are multiple resources,try-with-resourcesHow was it closed?

Here are some examples to see the results.

public class TryResourceDemo1 implements AutoCloseable {    public void doSomething(){
        System.out.println("do something 1");
    }    @Override
    public void close() throws Exception {
        System.out.println("resource 1 is closed");
    }
}
public class TryResourceDemo2 implements AutoCloseable {    public void doSomething(){
        System.out.println("do something 2");
    }    @Override
    public void close() throws Exception {
        System.out.println("resource 2 is closed");
    }
}
public class TryResourceDemoTest {    public static void main(String[] args) {
        try(TryResourceDemo1 demo1 = new TryResourceDemo1();
            TryResourceDemo2 demo2 = new TryResourceDemo2()) {
            System.out.println("do...");
            demo1.doSomething();
            demo2.doSomething();
        } catch(Exception ex) {
            ex.printStackTrace();
        }
    }
}

The running result is as follows:

do...
do something 1
do something 2
resource 2 is closed
resource 1 is closed

From the results, it can be seen that,tryThe last resource used in a statement is the earliest to be closed.

You can find the principle behind this from the decompiled code!

4、 Exception handling mechanism

Under normal circumstances,tryAt the end of the statement, relevant resources will be closed. If an exception occurs during internal execution of the statement and we explicitly call itfinallyWhat is the method and the order of execution?

Let’s continue with the example below to see the results.

public class TryThrowResourceDemoTest {    public static void main(String[] args) {
        AutoCloseable obj1 = null;
        AutoCloseable obj2 = null;
        try (TryResourceDemo1 demo1 = new TryResourceDemo1();
             TryResourceDemo2 demo2 = new TryResourceDemo2();) {
            System.out.println("do...");
            obj1 = demo1;
            System.out.println(1 / 0);
            obj2 = demo2;
            System.out.println("over...");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                System.out.println("before finally close");
                if (obj1 != null) {
                    obj1.close();
                }
                if (obj2 != null) {
                    obj2.close();
                }
                System.out.println("after finally close");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

The running result is as follows:

do...
resource 2 is closed
resource 1 is closed
before finally close
resource 1 is closed
after finally close
java.lang.ArithmeticException: / by zero
 at com.example.java.trywithresources.a.TryThrowResourceDemoTest.main(TryThrowResourceDemoTest.java:18)

It can be clearly seen that the following conclusion can be drawn:

  • 1. As long as it is achievedAutoCloseableThe class of the interface, and intryDeclared object variables intryAfter the end, Regardless of whether an abnormality occurs,closeAll methods will be called

  • Secondly, intryThe later an object is declared, the earlier it will be declaredclosefall

  • 3.tryAutomatically called after completioncloseMethod, this action will be earlier thanfinallyThe method called in

5、 Suppress abnormal handling

In most cases, we usually don’t worry about resourcescloseAn exception will occur, assuming now thattryThe resource object declared in, when executedcloseWhat is the execution order of the method when throwing exceptions? How can we obtain this anomaly?

Seeing is believing, let’s take an example below to see the results.

public class TryThrowableResourceDemo1 implements AutoCloseable {    public void doSomething(){
        System.out.println("do something 1");
        throw new NullPointerException("TryThrowableResourceDemo1: doSomething() NullPointerException");
    }    @Override
    public void close() throws Exception {
        System.out.println("TryThrowableResourceDemo1 is closed");
        throw new NullPointerException("TryThrowableResourceDemo1: close() NullPointerException");
    }
}
public class TryThrowableResourceDemo2 implements AutoCloseable {    public void doSomething(){
        System.out.println("do something 2");
        throw new NullPointerException("TryThrowableResourceDemo2: doSomething() NullPointerException");
    }    @Override
    public void close() throws Exception {
        System.out.println("TryThrowableResourceDemo2 is closed");
        throw new NullPointerException("TryThrowableResourceDemo2: close() NullPointerException");
    }
}
public class TryThrowableResourceDemoTest {    public static void main(String[] args) {
        try (TryThrowableResourceDemo1 demo1 = new TryThrowableResourceDemo1();
             TryThrowableResourceDemo2 demo2 = new TryThrowableResourceDemo2()) {
            System.out.println("do...");
            demo1.doSomething();
            demo2.doSomething();
        } catch (Exception e) {
            System.out.println("gobal: exception");
            System.out.println(e.getMessage());
            Throwable[] suppressed = e.getSuppressed();
            for (int i = 0; i < suppressed.length; i++){
                System.out.println(suppressed[i].getMessage());
            }
        }
    }
}

The running result is as follows:

do...
do something 1
TryThrowableResourceDemo2 is closed
TryThrowableResourceDemo1 is closed
gobal: exception
TryThrowableResourceDemo1: doSomething() NullPointerException
TryThrowableResourceDemo2: close() NullPointerException
TryThrowableResourceDemo1: close() NullPointerException

From the running results, we can clearly see that fortryWe can detect exceptions within the statement block throughe.getMessage()Obtain, forclose()The exceptions thrown by the method are actually specially handled by the compiler and placed in the collection array. Therefore, we need toe.getSuppressed()Method to obtain.

The specific decompiled code is as follows:

public class TryThrowableResourceDemoTest {        public static void main(String[] args) {
        try {
            TryThrowableResourceDemo1 demo1 = new TryThrowableResourceDemo1();
            Throwable var34 = null;            try {
                TryThrowableResourceDemo2 demo2 = new TryThrowableResourceDemo2();
                Throwable var4 = null;                try {
                    System.out.println("do...");
                    demo1.doSomething();
                    demo2.doSomething();
                } catch (Throwable var29) {
                    var4 = var29;
                    throw var29;
                } finally {
                    if (demo2 != null) {
                        if (var4 != null) {
                            try {
                                demo2.close();
                            } catch (Throwable var28) {
                                var4.addSuppressed(var28);
                            }
                        } else {
                            demo2.close();
                        }
                    }                }
            } catch (Throwable var31) {
                var34 = var31;
                throw var31;
            } finally {
                if (demo1 != null) {
                    if (var34 != null) {
                        try {
                            demo1.close();
                        } catch (Throwable var27) {
                            var34.addSuppressed(var27);
                        }
                    } else {
                        demo1.close();
                    }
                }            }
        } catch (Exception var33) {
            System.out.println("gobal: exception");
            System.out.println(var33.getMessage());
            Throwable[] suppressed = var33.getSuppressed();            for(int i = 0; i < suppressed.length; ++i) {
                System.out.println(suppressed[i].getMessage());
            }
        }    }
}

6、 Close the pit of resources

In practical use, whether it is usingtry-with-resourceProgramming or usingtry-catch-finallyprogramming It is necessary to understand the resourcescloseThe internal implementation logic of the method, otherwise it may still lead to resource leakage

For example, Java BIO employs a large number of decorator patterns. When calling the close method of the decorator, it is essentially calling the close method of the flow object wrapped by the decorator. For example:

public class TryWithResource {
    public static void main(String[] args) {
        try (FileInputStream fin = new FileInputStream(new File("input.txt"));
                GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(new File("out.txt")))) {
            byte[] buffer = new byte[4096];
            int read;
            while ((read = fin.read(buffer)) != -1) {
                out.write(buffer, 0, read);
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }
}

In the above code, we derive fromFileInputStreamRead bytes and write them toGZIPOutputStreamIn the middle.GZIPOutputStreamActually, it isFileOutputStreamThe decorator.

becausetry-with-resourceThe features of the code will be included later in the actual compiled codefinallyCode block and call it insidefin.close()Method andout.close()method.

Let’s take another lookGZIPOutputStreamClass ofclosemethod.

public void close() throws IOException {
    if (!closed) {
        finish();
        if (usesDefaultDeflater)
            def.end();
        out.close();
        closed = true;
    }
}

In callingoutThe variablecloseBefore the method,GZIPOutputStreamI also did itfinishOperation, this operation will continueFileOutputStreamWrite compressed information in the middle. If an exception occurs at this time, thenout.close()The method will be skipped, andoutVariables actually represent the decorated onesFileOutputStreamClass, this is the lowest level resource shutdown method

The correct approach should be totry-with-resourceDeclare the lowest level resources separately to ensure correspondingcloseThe method can definitely be called. In the previous example, we need to declare each separatelyFileInputStreamas well asFileOutputStreamChange it to the following way:

public class TryWithResource {
    public static void main(String[] args) {
        try (FileInputStream fin = new FileInputStream(new File("input.txt"));
                FileOutputStream fout = new FileOutputStream(new File("out.txt"));
                GZIPOutputStream out = new GZIPOutputStream(fout)) {
            byte[] buffer = new byte[4096];
            int read;
            while ((read = fin.read(buffer)) != -1) {
                out.write(buffer, 0, read);
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }
}

The compiler will automatically generatefout.close()The code will definitely ensure that the real stream is closed.

7、 Summary

When dealing with resources that must be closed, usetry-with-resourcesSentence substitutiontry-catch-finallyStatements, you will be amazed to find that the code written is more concise and clear, while also eliminating the hassle of manually releasing resources explicitly.

Therefore, in the actual programming process, it is recommended that everyone adopt this method of writing, while paying attention to itcloseInternal implementation logic of the method to avoid resource leakage and service downtime!

返回博客
New package in Go 1.23: unique

New package in Go 1.23: unique

Go 1.23 introduces unique package for value normalization, enhancing memory efficiency and equality checks. Learn how "interning" works with unique and its benefits for Go developers.

How to cache well in Go

How to cache well in Go

Optimize Go app performance with caching strategies. Learn local vs distributed cache, memory management, and eviction policies. Enhance efficiency with Go's new unique package.

The noCopy strategy you should know in Golang

The noCopy strategy you should know in Golang

Discover the importance of noCopy in Golang development. Learn how it prevents accidental copying of critical structures like sync primitives. Enhance your Go code safety and efficiency.