As we all know, all opened system resources, such as streams, files or Socket connections, need to be manually closed by developers. Otherwise, as the program continues to run, resource leakage will accumulate. Major production accident.
In the world of Java, there is a kung fu called finally, which can ensure that when you go crazy while practicing martial arts, you can still do some self-rescue operations. In ancient times, code that handled resource closing was usually written in finally blocks. However, if you open multiple resources at the same time, a nightmare scenario will occur:
public class Demo { 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 { if (bin != null) { try { bin.close(); } catch (IOException e) { e.printStackTrace(); } finally { if (bout != null) { try { bout.close(); } catch (IOException e) { e.printStackTrace(); } } } } } } }
Oh My God! ! ! There are more codes to close resources than business codes! ! ! This is because we not only need to close BufferedInputStream
, but we also need to ensure that if an exception occurs when closing BufferedInputStream
, BufferedOutputStream
must also be closed correctly. So we have to resort to the finally method of nesting finally. It can be imagined that the more resources are opened, the deeper the nesting in finally will be! ! !
What’s even more disgusting is that Pythonprogrammer faced this problem and actually smiled and said charmingly: “We don’t have to think about this at all~ ”:
#But brothers, don’t panic! We can use the new try-with-resource syntactic sugar in Java 1.7 to open resources, without the need for programmers to write resources to close the code themselves. Mom no longer has to worry about my handwriting breaking! We use try-with-resource to rewrite the example just now:
public class TryWithResource { 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(); } } }
Isn’t it very simple? Isn’t it exciting? No more being looked down upon by Python programmers! Well, the implementation principle and internal mechanism will be explained in detail below.
In order to cooperate with try-with-resource, the resource must implement the AutoClosable
interface. The implementation class of this interface needs to override the close
method:
public class Connection implements AutoCloseable { public void sendData() { System.out.println("正在发送数据"); } @Override public void close() throws Exception { System.out.println("正在关闭连接"); } }
Calling class:
public class TryWithResource { public static void main(String[] args) { try (Connection conn = new Connection()) { conn.sendData(); } catch (Exception e) { e.printStackTrace(); } } }
Output result after running:
正在发送数据 正在关闭连接
Through the result, we can See, the close method is automatically called.
So how is this done? I believe you who are smart must have guessed that, in fact, all this is caused by the compiler master. Let's decompile the class file of the example just now:
public class TryWithResource { public TryWithResource() { } public static void main(String[] args) { try { Connection e = new Connection(); Throwable var2 = null; try { e.sendData(); } catch (Throwable var12) { var2 = var12; throw var12; } finally { if(e != null) { if(var2 != null) { try { e.close(); } catch (Throwable var11) { var2.addSuppressed(var11); } } else { e.close(); } } } } catch (Exception var14) { var14.printStackTrace(); } } }
Did you see that in lines 15 to 27, the compiler automatically generated the finally block for us and called the close method of the resource in it, so in the example The close method will be executed at runtime.
I believe that you who are careful must have discovered that the code just decompiled (line 21) has one more code than the code written in ancient timesaddSuppressed
. In order to understand the purpose of this code, let's slightly modify the example just now: we change the code just now back to the way of manually closing exceptions in ancient times, and in the sendData
and close
methods Throwing exception:
public class Connection implements AutoCloseable { public void sendData() throws Exception { throw new Exception("send data"); } @Override public void close() throws Exception { throw new MyException("close"); } }
Modify the main method:
public class TryWithResource { public static void main(String[] args) { try { test(); } catch (Exception e) { e.printStackTrace(); } } private static void test() throws Exception { Connection conn = null; try { conn = new Connection(); conn.sendData(); } finally { if (conn != null) { conn.close(); } } } }
After running, we found:
basic.exception.MyException: close at basic.exception.Connection.close(Connection.java:10) at basic.exception.TryWithResource.test(TryWithResource.java:82) at basic.exception.TryWithResource.main(TryWithResource.java:7) ......
Okay, here comes the problem, because we once Only one exception can be thrown, so what you see at the top level is the last exception thrown - that is, MyException
thrown by the close
method, and sendData
The thrown Exception
is ignored. This is called exception shielding. Due to the loss of exception information, exception masking may make certain bugs extremely difficult to find. Programmers have to work overtime to find bugs. How can we not eliminate such a cancer! Fortunately, in order to solve this problem, starting from Java 1.7, the big guys have added the addSuppressed
method to the Throwable
class, which supports attaching one exception to another exception to avoid exceptions. shield. So in what format will the blocked exception information be output? Let’s run the main method just wrapped with try-with-resource again:
java.lang.Exception: send data at basic.exception.Connection.sendData(Connection.java:5) at basic.exception.TryWithResource.main(TryWithResource.java:14) ...... Suppressed: basic.exception.MyException: close at basic.exception.Connection.close(Connection.java:10) at basic.exception.TryWithResource.main(TryWithResource.java:15) ... 5 more
You can see that there is an additional Suppressed
prompt in the exception information, telling us that this exception is actually caused by two It consists of an exception, MyException
is a Suppressed exception. congratulations!
In the process of using try-with-resource, you must understand the internal implementation logic of the close
method of the resource. Otherwise, resources may still be leaked.
For example, a large number of Decorator patterns are used in Java BIO. When the close
method of the decorator is called, it is essentially calling the close
method of the stream wrapped inside 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(); } } }
在上述代码中,我们从FileInputStream
中读取字节,并且写入到GZIPOutputStream
中。GZIPOutputStream
实际上是FileOutputStream
的装饰器。由于try-with-resource的特性,实际编译之后的代码会在后面带上finally代码块,并且在里面调用fin.close()方法和out.close()方法。我们再来看GZIPOutputStream
类的close方法:
public void close() throws IOException { if (!closed) { finish(); if (usesDefaultDeflater) def.end(); out.close(); closed = true; } }
我们可以看到,out变量实际上代表的是被装饰的FileOutputStream
类。在调用out变量的close
方法之前,GZIPOutputStream
还做了finish
操作,该操作还会继续往FileOutputStream
中写压缩信息,此时如果出现异常,则会out.close()
方法被略过,然而这个才是最底层的资源关闭方法。正确的做法是应该在try-with-resource中单独声明最底层的资源,保证对应的close
方法一定能够被调用。在刚才的例子中,我们需要单独声明每个FileInputStream
以及FileOutputStream
:
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(); } } }
由于编译器会自动生成fout.close()
的代码,这样肯定能够保证真正的流被关闭。
怎么样,是不是很简单呢,如果学会了话
The above is the detailed content of Detailed introduction to try-with-resource sample code in Java. For more information, please follow other related articles on the PHP Chinese website!