try-with-resouces

“二哥,终于等到你讲 try-with-resouces 了!”三妹夸张的表情让我有些吃惊。

“三妹,不要激动呀!开讲之前,我们还是要来回顾一下 try–catch-finally,好做个铺垫。”我说,“来看看这段代码吧。”

  1. public class TrycatchfinallyDecoder {
  2. public static void main(String[] args) {
  3. BufferedReader br = null;
  4. try {
  5. String path = TrycatchfinallyDecoder.class.getResource("/牛逼.txt").getFile();
  6. String decodePath = URLDecoder.decode(path,"utf-8");
  7. br = new BufferedReader(new FileReader(decodePath));
  8. String str = null;
  9. while ((str =br.readLine()) != null) {
  10. System.out.println(str);
  11. }
  12. } catch (IOException e) {
  13. e.printStackTrace();
  14. } finally {
  15. if (br != null) {
  16. try {
  17. br.close();
  18. } catch (IOException e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. }
  23. }
  24. }

“我简单来解释下。”等三妹看完这段代码后,我继续说,“在 try 块中读取文件中的内容,并一行一行地打印到控制台。如果文件找不到或者出现 IO 读写错误,就在 catch 中捕获并打印错误的堆栈信息。最后,在 finally 中关闭缓冲字符读取器对象 BufferedReader,有效杜绝了资源未被关闭的情况下造成的严重性能后果。”

“在 Java 7 之前,try–catch-finally 的确是确保资源会被及时关闭的最佳方法,无论程序是否会抛出异常。”

三妹点了点头,表示同意。

“不过,这段代码还是有些臃肿,尤其是 finally 中的代码。”我说,“况且,try–catch-finally 至始至终存在一个严重的隐患:try 中的 br.readLine() 有可能会抛出 IOException,finally 中的 br.close() 也有可能会抛出 IOException。假如两处都不幸地抛出了 IOException,那程序的调试任务就变得复杂了起来,到底是哪一处出了错误,就需要花一番功夫,这是我们不愿意看到的结果。”

“我来给你演示下,三妹。”

“首先,我们来定义这样一个类 MyfinallyReadLineThrow,它有两个方法,分别是 readLine()close(),方法体都是主动抛出异常。”

  1. class MyfinallyReadLineThrow {
  2. public void close() throws Exception {
  3. throw new Exception("close");
  4. }
  5. public void readLine() throws Exception {
  6. throw new Exception("readLine");
  7. }
  8. }

“然后在 main() 方法中使用 try-catch-finally 的方式调用 MyfinallyReadLineThrow 的 readLine()close() 方法。”

  1. public class TryfinallyCustomReadLineThrow {
  2. public static void main(String[] args) throws Exception {
  3. MyfinallyReadLineThrow myThrow = null;
  4. try {
  5. myThrow = new MyfinallyReadLineThrow();
  6. myThrow.readLine();
  7. } finally {
  8. myThrow.close();
  9. }
  10. }
  11. }

运行上述代码后,错误堆栈如下所示:

  1. Exception in thread "main" java.lang.Exception: close
  2. at com.cmower.dzone.trycatchfinally.MyfinallyOutThrow.close(TryfinallyCustomOutThrow.java:17)
  3. at com.cmower.dzone.trycatchfinally.TryfinallyCustomOutThrow.main(TryfinallyCustomOutThrow.java:10)

“看出来问题了吗,三妹?”

“啊?readLine() 方法的异常信息竟然被 close() 方法的堆栈信息吃了!”

“不错啊,三妹,火眼金睛,的确,这会让我们误以为要调查的目标是 close() 方法而不是 readLine() 方法——尽管它也是应该怀疑的对象。”

“但有了 try-with-resources 后,这些问题就迎刃而解了。前提条件只有一个,就是需要释放的资源(比如 BufferedReader)实现了 AutoCloseable 接口。”

  1. try (BufferedReader br = new BufferedReader(new FileReader(decodePath));) {
  2. String str = null;
  3. while ((str =br.readLine()) != null) {
  4. System.out.println(str);
  5. }
  6. } catch (IOException e) {
  7. e.printStackTrace();
  8. }

“你瞧,三妹,finally 块消失了,取而代之的是把要释放的资源写在 try 后的 () 中。如果有多个资源(BufferedReader 和 PrintWriter)需要释放的话,可以直接在 () 中添加。”

  1. try (BufferedReader br = new BufferedReader(new FileReader(decodePath));
  2. PrintWriter writer = new PrintWriter(new File(writePath))) {
  3. String str = null;
  4. while ((str =br.readLine()) != null) {
  5. writer.print(str);
  6. }
  7. } catch (IOException e) {
  8. e.printStackTrace();
  9. }

“如果想释放自定义资源的话,只要让它实现 AutoCloseable 接口,并提供 close() 方法即可。”

  1. public class TrywithresourcesCustom {
  2. public static void main(String[] args) {
  3. try (MyResource resource = new MyResource();) {
  4. } catch (Exception e) {
  5. e.printStackTrace();
  6. }
  7. }
  8. }
  9. class MyResource implements AutoCloseable {
  10. @Override
  11. public void close() throws Exception {
  12. System.out.println("关闭自定义资源");
  13. }
  14. }

来看一下代码运行后的输出结果:

  1. 关闭自定义资源

“好神奇呀!”三妹欣喜若狂,“在 try () 中只是 new 了一个 MyResource 的对象,其他什么也没干,close() 方法就执行了!”

“想知道为什么吗?三妹。”

“当然想啊。”

“来看看反编译后的字节码吧。”

  1. class MyResource implements AutoCloseable {
  2. MyResource() {
  3. }
  4. public void close() throws Exception {
  5. System.out.println("关闭自定义资源");
  6. }
  7. }
  8. public class TrywithresourcesCustom {
  9. public TrywithresourcesCustom() {
  10. }
  11. public static void main(String[] args) {
  12. try {
  13. MyResource resource = new MyResource();
  14. resource.close();
  15. } catch (Exception var2) {
  16. var2.printStackTrace();
  17. }
  18. }
  19. }

“啊,原来如此。编译器主动为 try-with-resources 进行了变身,在 try 中调用了 close() 方法。”

“是这样的。接下来,我们在 MyResourceOut 类中再添加一个 out() 方法。”

  1. class MyResourceOut implements AutoCloseable {
  2. @Override
  3. public void close() throws Exception {
  4. System.out.println("关闭自定义资源");
  5. }
  6. public void out() throws Exception{
  7. System.out.println("沉默王二,一枚有趣的程序员");
  8. }
  9. }

“这次,我们在 try 中调用一下 out() 方法。”

  1. public class TrywithresourcesCustomOut {
  2. public static void main(String[] args) {
  3. try (MyResourceOut resource = new MyResourceOut();) {
  4. resource.out();
  5. } catch (Exception e) {
  6. e.printStackTrace();
  7. }
  8. }
  9. }

“再来看一下反编译的字节码。”

  1. public class TrywithresourcesCustomOut {
  2. public TrywithresourcesCustomOut() {
  3. }
  4. public static void main(String[] args) {
  5. try {
  6. MyResourceOut resource = new MyResourceOut();
  7. try {
  8. resource.out();
  9. } catch (Throwable var5) {
  10. try {
  11. resource.close();
  12. } catch (Throwable var4) {
  13. var5.addSuppressed(var4);
  14. }
  15. throw var5;
  16. }
  17. resource.close();
  18. } catch (Exception var6) {
  19. var6.printStackTrace();
  20. }
  21. }
  22. }

“这次,catch 块主动调用了 resource.close(),并且有一段很关键的代码 var5.addSuppressed(var4)。”

“这是为了什么呢?”三妹问。

“当一个异常被抛出的时候,可能有其他异常因为该异常而被抑制住,从而无法正常抛出。这时可以通过 addSuppressed() 方法把这些被抑制的方法记录下来,然后被抑制的异常就会出现在抛出的异常的堆栈信息中,可以通过 getSuppressed() 方法来获取这些异常。这样做的好处是不会丢失任何异常,方便我们进行调试。”我说。

“有没有想到之前的那个例子——在 try-catch-finally 中,readLine() 方法的异常信息竟然被 close() 方法的堆栈信息吃了。现在有了 try-with-resources,再来看看和 readLine() 方法一致的 out() 方法会不会被 close() 吃掉吧。”

  1. class MyResourceOutThrow implements AutoCloseable {
  2. @Override
  3. public void close() throws Exception {
  4. throw new Exception("close()");
  5. }
  6. public void out() throws Exception{
  7. throw new Exception("out()");
  8. }
  9. }

“调用这 2 个方法。”

  1. public class TrywithresourcesCustomOutThrow {
  2. public static void main(String[] args) {
  3. try (MyResourceOutThrow resource = new MyResourceOutThrow();) {
  4. resource.out();
  5. } catch (Exception e) {
  6. e.printStackTrace();
  7. }
  8. }
  9. }

“程序输出的结果如下所示。”

  1. java.lang.Exception: out()
  2. at com.cmower.dzone.trycatchfinally.MyResourceOutThrow.out(TrywithresourcesCustomOutThrow.java:20)
  3. at com.cmower.dzone.trycatchfinally.TrywithresourcesCustomOutThrow.main(TrywithresourcesCustomOutThrow.java:6)
  4. Suppressed: java.lang.Exception: close()
  5. at com.cmower.dzone.trycatchfinally.MyResourceOutThrow.close(TrywithresourcesCustomOutThrow.java:16)
  6. at com.cmower.dzone.trycatchfinally.TrywithresourcesCustomOutThrow.main(TrywithresourcesCustomOutThrow.java:5)

“瞧,这次不会了,out() 的异常堆栈信息打印出来了,并且 close() 方法的堆栈信息上加了一个关键字 Suppressed,一目了然。”

“三妹,怎么样?是不是感觉 try-with-resouces 好用多了!我来简单总结下哈,在处理必须关闭的资源时,始终有限考虑使用 try-with-resources,而不是 try–catch-finally。前者产生的代码更加简洁、清晰,产生的异常信息也更靠谱。”

“靠谱!”三妹说。