你真的理解 Java 中的 try_catch_finally 嗎?

原文鏈接:https://mp.weixin.qq.com/s/aXaQB35I_iP4kRjoXr_1MQ

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 不會被執行的情況


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

關注公衆號:「Java 知己」,每天更新Java知識哦,期待你的到來!

  • 發送「1024」,免費領取 30 本經典編程書籍。
  • 發送「Group」,與 10 萬程序員一起進步。
  • 發送「面試」,領取BATJ面試資料、面試視頻攻略。
  • 發送「JavaEE 實戰」,領取《JavaEE 實戰》系列視頻教程。
  • 發送「玩轉算法」,領取《玩轉算法》系列視頻教程。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章