你真的理解 Java 中的 try_catch_finally 嗎?

  • 2019 年 10 月 5 日
  • 筆記
Java知己

try…catch…finally恐怕是大家再熟悉不過的語句了,而且感覺用起來也是很簡單,邏輯上似乎也是很容易理解。不過,我親自體驗的「教訓」告訴我,這個東西可不是想像中的那麼簡單、聽話。不信?

看幾個例子,回顧一下執行順序


例子 1 無異常,finally 中的 return 會導致提前返回

public static String test() {      try {          System.out.println("try");          return "return in try";      } catch(Exception e) {          System.out.println("catch");          return "return in catch";      } finally {          System.out.println("finally");          return "return in finally";      }  }

調用 test() 的結果:

try  finally  return in finally

例子 2 無異常,try 中的 return 會導致提前返回

public static String test() {      try {          System.out.println("try");          return "return in try";      } catch(Exception e) {          System.out.println("catch");      } finally {          System.out.println("finally");      }      return "return in function";  }

調用 test() 的結果:

try  finally  return in try

例子 3 有異常,finally 中的 return 會導致提前返回

public static String test() {          try {              System.out.println("try");              throw new Exception();          } catch(Exception e) {              System.out.println("catch");              return "return in catch";          } finally {              System.out.println("finally");              return "return in finally";          }      }

調用 test() 的結果:

try  catch  finally  return in finally

例子 4 有異常,catch 中的 return 會導致提前返回

public static String test() {      try {          System.out.println("try");          throw new Exception();      } catch(Exception e) {          System.out.println("catch");          return "return in catch";      } finally {          System.out.println("finally");      }  }

調用 test() 的結果:

try  catch  finally  return in catch

例子 4 有異常,不會提前返回

public static String test() {      try {          System.out.println("try");          throw new Exception();      } catch(Exception e) {          System.out.println("catch");      } finally {          System.out.println("finally");      }      return "return in function";  }

調用 test() 的結果:

try  catch  finally  return in function

小結

上面這幾個例子,大多數人已經非常了解。同時也衍生出一些理論,比如不要在 finally 中 return 等,不再贅述。

再看幾個例子,返回值是否符合你的預期?


例子 1

public static int test() {      try {          return 1;      } finally {          return 2;      }  }

返回值:2

說明:與我們上面的例子一致,finally 中的 return 導致提前返回,try 中的 return1 不會被執行。

附編譯後的程式碼:

public static int test() {      try {          boolean var0 = true;          return 2;      } finally {          ;      }  }

可以看到編譯器做過優化,同時驗證了 boolean 類型在底層是用 int 實現的,但注意你在源碼中直接給 int 行賦值 true 或 false 是不被允許的。

例子 2

public static int test() {      int i;      try {          i = 3;      } finally {          i = 5;      }      return i;  }

返回值:5

說明:執行 try 中的程式碼後,再執行 finally 中的程式碼,最終 i 被賦值為 5,最後返回

附編譯後的程式碼:

public static int test() {      boolean var0 = true;      byte i;      try {          var0 = true;      } finally {          i = 5;      }      return i;  }

同樣可以看出,編譯器做了一些優化。

正是金九銀十跳槽季,為大家收集了2019年最新的面試資料,有文檔、有攻略、有影片。有需要的同學可以在公眾號【Java知己】,發送【面試】領取最新面試資料攻略!

例子 3

public static int test() {      int i = 1;      try {          i = 3;          return i;      } finally {          i = 5;      }  }

返回值:3

這個例子稍微有點意思,按我們通常的思維,應該還是返回 5,畢竟 finally 中把 i 賦值為 5 了嘛,然後由 try 中的 return 返回。然而很不幸,返回值是 3。

為什麼呢?先看一下編譯後的程式碼:

public static int test() {      boolean var0 = true;      byte var1;      try {          int i = 3;          var1 = i;      } finally {          var0 = true;      }      return var1;  }

我們會發現,finally 中的程式碼塊不起作用。不知你是否想起一點:Java 中是按值傳遞的,finally 中的 i 只是一個局部變數,finally 塊執行完畢後,局部變數便不復存在。

接著看例子:

例子 4

public static List test() {      List<Integer> list = new ArrayList<>();      try {          list.add(1);          return list;      } finally {          list.add(2);      }  }

返回:包含 1 和 2 兩個元素的 List 對象。

說明:這個例子中,基本類型 int 被替換為引用類型 List,雖然 list 是按值傳遞,但它內部的狀態可變(體現在這裡,就是可以 add 元素)。擴展:finally 只能保證對象本身不可變,但無法保證對象內部狀態不可變。

附編譯後的程式碼:

public static List test() {      ArrayList list = new ArrayList();      ArrayList var1;      try {          list.add(1);          var1 = list; // 執行這一步操作後,var1和list指向同一個對象      } finally {          list.add(2);      }      return var1;  }

你現在應該覺得自己理解了,那麼再來看兩個例子:

例子 5

public static int test() {       try {           System.exit(0);       } finally {           return 2;       }   }

該函數沒有返回值。原因:jvm 提前退出了。

附編譯後的程式碼:

public static int test() {      try {          System.exit(0);          return 2;      } finally {          ;      }  }

例子 6

public static int test() {      try {          while(true) {             System.out.println("Infinite loop.");          }      } finally {          return 2;      }  }

由於 try 中的無限循環阻塞,永遠執行不到 finally 中的程式碼塊。

附編譯後的程式碼:

public static int test() {      try {          while(true) {              System.out.println("Infinite loop.");          }      } finally {          ;      }  }

小結

為了方便說明,只舉了 finally 程式碼塊的例子,catch 程式碼塊是類似的。

總結

執行順序:

  1. try 程式碼塊中 return 前面的部分   2. catch 程式碼塊中 return 前面的部分   3. finally 程式碼塊中 return 前面的部分   4. finally 的 return 或 catch 的 return 或 try 的 return。若前面的 return 被執行,會導致提前返回,同時後面的 return 被忽略。   5. 方法的其他部分

變數:

  注意 Java 的按值傳遞規則

特殊情況:

  注意 finally 不會被執行的情況   


「不積跬步,無以至千里」,希望未來的你能:有夢為馬 隨處可棲!加油,少年!