<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          與面試官聊try-catch-finally關(guān)閉資源,你的答案還是10年前的?

          共 7221字,需瀏覽 15分鐘

           ·

          2021-04-14 02:36

          前言

          有編程經(jīng)驗(yàn)的朋友都知道,在程序運(yùn)行中如果打開了一些資源,那么當(dāng)發(fā)生異常或程序結(jié)束時(shí)都需要進(jìn)行資源的關(guān)閉,不然會(huì)造成內(nèi)存溢出的問題。

          曾經(jīng),關(guān)于try-catch-finally的使用也是面試題中的一個(gè)熱點(diǎn)問題。隨著JDK7的發(fā)布,情況好像有些變化了,處理資源關(guān)閉的方式更加方便了。但如果你的使用方式依舊停留在十年前,那這篇文章中講到的知識點(diǎn)值得你一讀。最重要的是底層原理分析部分。

          try-catch-finally傳統(tǒng)處理模式

          在JDK7之前,我們對異常和資源關(guān)閉的處理,通常是通過下面的形式來實(shí)現(xiàn)的:

          @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();
          }
          }
          }

          首先,通過try-catch來捕獲異常,并在catch代碼塊中對異常進(jìn)行處理(比如打印日志等);

          其次,在finally代碼塊中對打開的資源進(jìn)行關(guān)閉。因?yàn)闊o論程序是否發(fā)生異常,finally代碼塊是必然會(huì)被執(zhí)行的,這也就保證了資源的關(guān)閉。

          當(dāng)你寫了多年的代碼,上面的寫法也已經(jīng)牢記于心,但如果用JDK7及以上版本,且IDE中安裝了一些代碼規(guī)范的插件,在try上面會(huì)有如下提示:

          'try' can use automatic resource management 

          提示告訴你,try中的代碼可以使用自動(dòng)資源管理了。那我們就來看看它是如何實(shí)現(xiàn)自動(dòng)管理的呢。

          JDK7的資源關(guān)閉方式

          JDK7中引入了一個(gè)新特性:“try-with-resource”。先將上面的代碼改造成新的實(shí)現(xiàn)方式:

          @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();
          }
          }

          在try后面添加一個(gè)小括號,在小括號內(nèi)聲明初始化操作的資源。此時(shí),我們再也不用寫finally代碼塊進(jìn)行資源的關(guān)閉了,JVM會(huì)替我們進(jìn)行資源管理,自動(dòng)關(guān)閉資源。

          如果需要聲明多個(gè)資源,則可以通過分號進(jìn)行分割:

          @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();
          }
          }

          那么是不是,所有的資源都可以被JVM自動(dòng)關(guān)閉呢?還真不是的,對應(yīng)的資源類要實(shí)現(xiàn)java.io.Closeable接口才行。比如上面的Scanner便是實(shí)現(xiàn)了此接口:

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

          自定義關(guān)閉實(shí)現(xiàn)

          既然實(shí)現(xiàn)java.io.Closeable接口的類可以享受自動(dòng)關(guān)閉資源的好處,那我們自定義類是否同樣享受這個(gè)福利呢?

          先定義一個(gè)MyResource類,實(shí)現(xiàn)java.io.Closeable接口:

          public class MyResource implements Closeable {

          public void hello(){
          System.out.println("Hello try-catch-resource");
          }

          @Override
          public void close() throws IOException {
          System.out.println("自定義的close方法被自動(dòng)調(diào)用了...");
          }
          }

          在自定義類中要實(shí)現(xiàn)close()方法。然后看一下使用時(shí)是否會(huì)被自動(dòng)關(guān)閉:

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

          執(zhí)行單元測試,輸入結(jié)果:

          Hello try-catch-resource
          自定義的close方法被自動(dòng)調(diào)用了...

          可以看到在調(diào)用hello方法之后,JVM自動(dòng)調(diào)用了close方法,完美的關(guān)閉了資源。

          底層實(shí)現(xiàn)

          了解我寫文章風(fēng)格的讀者都會(huì)知道,在寫一個(gè)知識點(diǎn)時(shí)我們不只會(huì)停留在表面,還要看一下它的底層實(shí)現(xiàn)。這里我們先將測試代碼簡化:

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

          然后對其class文件進(jìn)行反編譯,可以看到Java編譯器對這一些寫法的真正實(shí)現(xiàn):

          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();
          }

          }

          會(huì)發(fā)現(xiàn)雖然我們沒寫finally代碼塊進(jìn)行資源的關(guān)閉,但Java編譯器已經(jīng)幫我們做了處理??吹竭@里,你可能已經(jīng)意識到了,try-catch-resource這種寫法只是一個(gè)語法糖。

          但好像不僅僅如此,finally代碼中還包含了一個(gè)addSuppressed方法的調(diào)用,這又是怎么回事呢?下面來分析一下。

          避免異常覆蓋

          在上面的示例中,我們將MyResource的兩個(gè)方法進(jìn)行改造:

          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...");
          }

          }

          在兩個(gè)方法中都拋出異常,此時(shí),我們再來執(zhí)行一下傳統(tǒng)寫法的單元測試代碼:

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

          打印結(jié)果如下:

          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)
          //...

          你發(fā)現(xiàn)什么了?本來是hello方法先拋出了異常,然后執(zhí)行close方法又拋出了異常,但后面的異常信息將前面真正的異常信息給“隱藏”了。此時(shí)你去排查bug,是不是很困惑?最關(guān)鍵的異常信息被覆蓋了。

          那么,我們再來執(zhí)行一下try-catch-resource寫法的代碼:

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

          執(zhí)行結(jié)果如下:

          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

          此時(shí)hello方法中的異常信息和close方法中的異常信息全被打印出來了。而異常信息中多出的Suppressed提示便是通過Java編譯器自動(dòng)添加的addSuppressed方法的調(diào)用來實(shí)現(xiàn)的。此時(shí),再通過異常日志排查bug是不是簡單多了,編譯器是真為程序員著想啊。

          小結(jié)

          本文通過對try-catch-finally和try-with-resource兩種寫法的對比,得知try-with-resource是JDK7為我們提供的一個(gè)語法糖,可以讓我們的代碼更加簡潔,本質(zhì)上與try-catch-finally的效果一樣。同時(shí),try-with-resource寫法通過addSuppressed方法對異常覆蓋問題進(jìn)行了處理,更便于程序員排查bug。

          往期推薦

          SpringBoot配置升級,舊的已過時(shí),新的人未知

          GC時(shí)對象地址變了,hashCode如何保持不變?

          GC復(fù)制存活對象,它內(nèi)存地址變了么?

          啟動(dòng)Spring Boot時(shí),如果不設(shè)置內(nèi)存參數(shù)會(huì)如何?

          因?yàn)橐淮五礄C(jī),終于搞透了 Kafka 高可用原理!



          如果你覺得這篇文章不錯(cuò),那么,下篇通常會(huì)更好。添加微信好友,可備注“加群”(微信號:zhuan2quan)。

          一篇文章就看透技術(shù)本質(zhì)的人,
            和花一輩子都看不清的人,
            注定是截然不同的搬磚生涯。
          ▲ 按關(guān)注”程序新視界“,洞察技術(shù)內(nèi)幕
          瀏覽 73
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  色五月久久婷婷综合片丁香花 | 97资源超碰 | 久久久久久久人妻丝袜 | 亚洲国产欧美手机在线 | 国产精品毛片完整版 |