1, Background
In Java programming, if external resources are opened, such as file input / output stream, database connection, network connection (InputStream, OutputStream, java.sql.Connection), we must manually close these external resources after they are used. Because external resources are not managed by the JVM, you cannot enjoy the JVM's garbage collection mechanism. If we fail to ensure that external resources are closed at the right time during programming, it will lead to the leakage of external resources, the abnormal occupation of files, the overflow of connection pool caused by too many database connections and many other serious performance problems.
There are many ways to close. For example: finalizer, try catch finally, try with resources, etc.
The finalizer mechanism can be turned off, but its execution is unpredictable and may cause memory leakage, so it is generally not used. Therefore, the choice falls between try catch finally and try with resources.
2, Use try catch finally to close the resource
In order to ensure that external resources must be closed, the closing code is usually written to the finally code block. Of course, we must also pay attention to the exceptions that may be thrown when closing resources, so we have the following code:
public class FileTestTwo { public static void main(String[] args) { FileInputStream in = null; int length = 0; try { in = new FileInputStream("test.txt"); byte[] bytes = new byte[1024]; while ((length = in.read(bytes)) != -1) { System.out.println(new String(bytes, 0, length)); } } catch (IOException e) { e.printStackTrace(); } finally { if (null != in) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
The above code: read test through the file input stream Txt file, read 1024B at a time, print the read content to the screen every time, and finally close the stream in the finally statement.
Add a file output stream to the original code. As follows:
public class FileTestTwo { public static void main(String[] args) { FileInputStream in = null; FileOutputStream out = null; int length = 0; try { in = new FileInputStream("test.txt"); out = new FileOutputStream("test2.txt"); byte[] bytes = new byte[1024]; while ((length = in.read(bytes)) != -1) { out.write(bytes, 0, length); } } catch (IOException e) { e.printStackTrace(); } finally { if (null != in) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } finally { // Use finally here to prevent in The close () method throws an exception without executing out close() if (null != out) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } } } } }
From the above code, the more resources you open, the more codes you need to close, and even more than business codes. Because we not only need to close FileInputStream, but also need to ensure that in. Is executed close(); An exception occurred during code, and FileOutputStream can also be closed correctly. Therefore, you have to nest finally statements.
But it is worth noting that the more resources you open, the deeper the finally statement will be nested!!
3, Use try with resources to close resources
At jdk1 In 7, the try with resources syntax is added to realize the function of automatically closing external resources.
When using it, you must require that the handle object of external resources (such as FileInputStream object) implements the autoclosable interface.
So, how to use it?
Put the creation of the handle object of the external resource in the parentheses after the try. When the try catch code block is executed, Java will ensure that the close() method of the external resource is called.
The above code is modified with try with resources syntax, and the code is as follows:
public class FileThree { public static void main(String[] args) { try (FileInputStream in = new FileInputStream("test.txt"); FileOutputStream out = new FileOutputStream("test2.txt")) { int length = 0; byte[] bytes = new byte[1024]; while ((length = in.read(bytes)) != -1) { out.write(bytes, 0, length); } } catch (IOException e) { e.printStackTrace(); } } }
In this way, does it feel that the code is much cleaner. When the program leaves the try statement block after running, the resources in () will be closed automatically.
However, the key points of try with resources should be kept in mind:
- The class in try() must implement the autoclosable interface
- The resource declared in the try() code is implicitly declared final
- Multiple resources can be declared using semicolon separation
Manual customization
Create a new class and use the try with resources syntax.
Then this class needs to implement the AutoClosable interface. The implementation class of this interface needs to override the close() method. The code is as follows:
public class TestAutoClosable implements AutoCloseable { public void test() { System.out.println("test() Method called"); } @Override public void close() throws Exception { System.out.println("close() Method called"); } public static void main(String[] args) { try (TestAutoClosable test = new TestAutoClosable()) { test.test(); } catch (Exception e) { e.printStackTrace(); } } }
Run the above code:
test() Method called close() Method called
It is found that the TestAutoClosable#close() method is called (the code does not show that this close() method is called).
So, how is this done?
This depends on the implementation principle of try with resources
Implementation principle
Try with resources is not a new feature of the JVM, but JDK implements a syntax sugar. When you decompile the above code, you will find that for the JVM, it still sees the previous writing:
public class TestAutoClosable implements AutoCloseable { public TestAutoClosable() { } public void test() { System.out.println("test() Method called"); } public void close() throws Exception { System.out.println("close() Method called"); } public static void main(String[] args) { try { TestAutoClosable test = new TestAutoClosable(); Throwable var2 = null; try { test.test(); } catch (Throwable var12) { var2 = var12; throw var12; } finally { if (test != null) { if (var2 != null) { try { test.close(); } catch (Throwable var11) { var2.addSuppressed(var11); } } else { test.close(); } } } } catch (Exception var14) { var14.printStackTrace(); } } }
From the decompiled code, the compiler automatically generates a finally block for us and calls the close() method of the resource, so the close() method in the example will be executed at run time.
Abnormal shielding
From the decompiled code, we can see an addresssuppressed () method. What is the function of this method?
To clarify it, we modify the above example as follows:
public class TestAutoClosable implements AutoCloseable { public void test() throws Exception{ throw new Exception("test() Method called"); } @Override public void close() throws Exception { throw new MyException("close() Method called"); } public static void testClose() throws Exception{ TestAutoClosable test = null; try { test = new TestAutoClosable(); test.test(); } finally { if (null != test) { test.close(); } } } // test public static void main(String[] args) { try { testClose(); } catch (Exception e) { e.printStackTrace(); } } }
Finally, the console prints out:
java.lang.Exception: close() Method called at com.tiandy.zzc.design.file.TestAutoClosable.close(TestAutoClosable.java:20) at com.tiandy.zzc.design.file.TestAutoClosable.testClose(TestAutoClosable.java:45) at com.tiandy.zzc.design.file.TestAutoClosable.main(TestAutoClosable.java:31)
The problem is that we can only throw one Exception at a time, so what we see at the top layer is the last Exception thrown - that is, the Exception thrown by the close() method, and the Exception thrown by the test() method is ignored. This is called Exception shielding.
Due to the loss of exception information, exception masking may make some bug s extremely difficult to find. In order to solve this problem, starting from Java 1.7, the bosses added the addresssuppressed () method to the Throwable class, which supports attaching an exception to another exception, so as to avoid exception shielding.
What format will the masked exception information be output?
Let's run the main() method wrapped with try with resource again:
public static void main(String[] args) { try (TestAutoClosable test = new TestAutoClosable()) { test.testClose(); } catch (Exception e) { e.printStackTrace(); } }
Console print out:
java.lang.Exception: close() Method called at com.tiandy.zzc.design.file.TestAutoClosable.close(TestAutoClosable.java:20) at com.tiandy.zzc.design.file.TestAutoClosable.testClose(TestAutoClosable.java:45) at com.tiandy.zzc.design.file.TestAutoClosable.main(TestAutoClosable.java:25) Suppressed: java.lang.Exception: close() Method called at com.tiandy.zzc.design.file.TestAutoClosable.close(TestAutoClosable.java:20) at com.tiandy.zzc.design.file.TestAutoClosable.main(TestAutoClosable.java:26)
As you can see, there is a hint of Suppressed in the Exception information, which tells us that this Exception is actually composed of two exceptions. The Exception of the close() method is the Suppressed Exception.
summary
When dealing with resources that must be closed, always give priority to try with resources rather than try finally. The resulting code will be more concise and clear, and the generated exceptions will be more valuable, which try finally can't do.