Friends with programming experience know that if some resources are opened during the operation of the program, then resources need to be closed when exceptions occur or the program ends, otherwise it will cause memory overflow.

At one time, the use of try-catch-finally was a hot topic in interview questions. With the release of JDK7, things seem to have changed a bit, making it easier to handle resource shutdowns. But if you’re still using it 10 years ago, it’s worth reading this article. The most important part is the underlying principle analysis.

Try-catch-finally Traditional processing mode

Prior to JDK7, exceptions and resource closures were handled in the following way:

@Test public void testOldProcess() { Scanner scanner = null; try { scanner = new Scanner(new File("test.txt")); while (scanner.hasNext()) { System.out.println(scanner.nextLine()); } } catch (FileNotFoundException e) { e.printStackTrace(); } finally { if (scanner ! = null) { scanner.close(); }}}Copy the code

First, the exception is caught by try-catch and handled in a block of catch code (such as printing logs, etc.).

Second, close open resources in the finally code block. Because the finally block must be executed regardless of whether an exception occurs in the program, it ensures that the resource is closed.

If you have been writing code for many years, you will know this by heart, but if you are using JDK7 or higher, and you have installed some plug-in code specification in the IDE, you will see the following message on the try:

'try' can use automatic resource management 
Copy the code

The prompt tells you that the code in the try is ready to use automatic resource management. So let’s see how it implements automatic management.

How to close resources in JDK7

A new feature has been introduced in JDK7: “try-with-resource”. Let’s transform the above code into a new implementation:

@Test public void testNewProcess() { try (Scanner scanner = new Scanner(new File("test.txt"))) { while (scanner.hasNext()) { System.out.println(scanner.nextLine()); } } catch (FileNotFoundException e) { e.printStackTrace(); }}Copy the code

Add a parentheses after the try to declare the resource for the initialization operation. At this point, we no longer need to write a finally block to shut down the resource, the JVM will do the resource management for us and automatically shut down the resource.

If you need to declare more than one resource, you can split it with a semicolon:

@Test public void testNewProcess1() { try ( Scanner scanner = new Scanner(new File("test.txt")); Scanner scanner1 = new Scanner(new File("test1.txt"));) { while (scanner.hasNext()) { System.out.println(scanner.nextLine()); } while (scanner1.hasNext()) { System.out.println(scanner1.nextLine()); } } catch (FileNotFoundException e) { e.printStackTrace(); }}Copy the code

Can all resources be shut down automatically by the JVM? No, the corresponding resource class needs to implement the java.io.Closeable interface. For example, the Scanner above implements this interface:

public final class Scanner implements Iterator<String>, Closeable {//... }Copy the code

Custom closing implementation

Since classes that implement the java.io.Closeable interface can automatically close resources, do our custom classes also enjoy the same benefits?

Define a MyResource class that implements the java.io.Closeable interface:

public class MyResource implements Closeable { public void hello(){ System.out.println("Hello try-catch-resource"); } @override public void close() throws IOException {system.out.println (" The custom close method is automatically called..." ); }}Copy the code

The close() method is implemented in a custom class. Then see if it is automatically turned off when used:

@Test public void testMyResource() { try (MyResource resource = new MyResource();) { resource.hello(); } catch (IOException exception) { exception.printStackTrace(); }}Copy the code

Execute the unit test and enter the result:

Hello try-catch-resource custom close method is automatically called...Copy the code

As you can see, after calling Hello, the JVM automatically calls close, perfectly shutting down the resource.

The underlying implementation

Readers of my writing style will know that when we write about a knowledge point, we will not only look at the surface, but also look at the underlying implementation. Here we will simplify the test code:

public void testMyResource() { try (MyResource resource = new MyResource()) { resource.hello(); } catch (IOException e) { e.printStackTrace(); }}Copy the code

Then decompile the class file to see what the Java compiler actually does:

public void testMyResource() { try { MyResource resource = new MyResource(); Throwable var2 = null; try { resource.hello(); } catch (Throwable var12) { var2 = var12; throw var12; } finally { if (resource ! = null) { if (var2 ! = null) { try { resource.close(); } catch (Throwable var11) { var2.addSuppressed(var11); } } else { resource.close(); } } } } catch (IOException var14) { var14.printStackTrace(); }}Copy the code

You’ll notice that although we didn’t write a finally block to close the resource, the Java compiler does it for us. Now, you probably realize that try-catch-resource is just a syntactic candy.

But it seems that more than that, the finally code also includes a call to the addSuppressed method. What about that? So let’s analyze it.

Avoid exception coverage

In the above example, we modified two methods of MyResource:

public class MyResource implements Closeable { public void hello(){ throw new RuntimeException("Resource throw Exception..." ); } @Override public void close() { throw new RuntimeException("Close method throw Exception..." ); }}Copy the code

Throw an exception in both methods. At this point, we will execute the unit test code in the traditional way:

@Test public void testOldMyResource() { MyResource resource = null; try { resource = new MyResource(); resource.hello(); } finally { if (resource ! = null) { resource.close(); }}}Copy the code

The print result is as follows:

java.lang.RuntimeException: Close method throw Exception...

	at com.secbro2.resource.MyResource.close(MyResource.java:19)
	at com.secbro2.resource.CloseMyResourcesTest.testOldMyResource(CloseMyResourcesTest.java:22)
    //...
Copy the code

What did you find? The hello method raises an exception first, and then the close method raises another exception, but the following exception message “hides” the previous real exception message. Are you confused when you go to check the bugs? The most critical exception information is overwritten.

So, let’s execute the try-catch-resource code again:

@Test public void testMyResource() { try (MyResource resource = new MyResource()) { resource.hello(); }}Copy the code

The result is as follows:

java.lang.RuntimeException: Resource throw Exception...

	at com.secbro2.resource.MyResource.hello(MyResource.java:14)
	at com.secbro2.resource.CloseMyResourcesTest.testMyResource(CloseMyResourcesTest.java:30)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
	Suppressed: java.lang.RuntimeException: Close method throw Exception...
		at com.secbro2.resource.MyResource.close(MyResource.java:19)
		at com.secbro2.resource.CloseMyResourcesTest.testMyResource(CloseMyResourcesTest.java:31)
		... 22 more
Copy the code

The exception information in the Hello method and the exception information in the close method are printed. The extra hint of Suppressed in the exception message is implemented through a call to the addSuppressed method automatically added by the Java compiler. At this point, it is much easier to check for bugs through the exception log, and the compiler is really for the sake of the programmer.

summary

Try-with-resource is a syntax that can be used in JDK7 to make our code more simple. The syntax is try-catch-finally and try-with-resource. Essentially the same effect as try-catch-finally. At the same time, the try-with-resource notation handled the exception override problem by means of addSuppressed method, which made it easier for programmers to troubleshoot bugs.