【Java面試提問解讀】二:異常解析
面試公司:位元組跳動西瓜視訊
面試崗位:後臺開發日常實習生
面試輪次:第一次面試
前言:企業級專案為了保持自身應用的魯棒性,應該非常重視異常處理,所以這一篇就從面試官對我的異常方向的提問著手解析Java中的異常。
Java標準異常分類
Java的標準異常繼承Throwable
,共分為兩種型別。
-
Error
:表示編譯時和系統錯誤,一般不需要程式設計師關注 -
Exception
:可以被丟擲的基本型別,屬於程式設計師需要關注並處理的型別。該基本型別分為兩種異常:
- 執行時異常,也就是RuntimeException
,該種異常是不強制程式設計師處理的異常。常見有NullPointerException
,ArrayIndexOutOfBoundsException
等。
- 編譯期異常,也就是除了RuntimeException
之外的所有異常,要求程式設計師必須對該異常進行處理,try-catch
或者throws
均可。常見有IOException
等。
throw
與throws
的區別
-
throw
用於語句內,表示丟擲異常。 -
throws
用於方法簽名上,表示該方法可能會丟擲哪些異常。 -
當
throw
一個執行時異常不需要用throws
宣告方法丟擲異常,而對於編譯期異常則需要在方法簽名上使用throws
新增方法的異常說明。 -
也就是說,
throws
是針對編譯期異常的關鍵詞
簡述finally
以及異常丟失現象
-
finally
的存在是因為希望無論try-catch
執行如何,都能執行某一段語句,如物件的狀態管理或是資源的清理。因此,finally
裡的程式碼在幾乎任何情況下都能得到執行 -
不能被執行的情況:
- 程式未執行到try
塊即退出或轉向
- 整個程式被強制結束,如使用者強行關閉或者System.exit(0)
或者斷電等等 -
finally
與return
的愛恨糾葛
這個感覺幾乎是面試熱點。兩者有個矛盾,一方面return
會導致當前方法執行被終止並返回,而另一方面finally
裡的程式碼是幾乎必然執行的。對於這個情況,我們要記住finally
是必然執行的即可。
因此會出現一種情況,方法先return
某一個值A,然後finally
裡return
另一個值B(關於這個值是否是原始型還是引用型這裡不深入)。對於這種情況的理解可以是這樣的,首先return
申請一塊記憶體,並將A寫入該記憶體;然後finally
執行,這裡的return
對上述記憶體進行修改(也有可能是重新申請記憶體),並寫入B;最後到達程式出口,將B返回。可參考以下程式碼。
public class Test { public static int test(){ int i = 1; try{ i = 2; return i; }finally { i = 3; return i; } } public static void main(String[] args){ System.out.println(test()); } } /** * output: 3 */
同時對於以上情況,一定要注意一點,也就是return
必然申請新的記憶體,並把原值複製,而不是將原值所在記憶體返回,同時需要聯絡原始型和引用型的區別進行分析。可參考以下程式碼。
public class Test { public static int test(){ int i = 1; try{ i = 2; return i; }finally { i = 3; } } public static void main(String[] args){ System.out.println(test()); } } /** * output: 2 */
public class Test { public static StringBuilder test(){ StringBuilder s = new StringBuilder(); try{ s.append("First"); return s; }finally { s.append(" Second"); } } public static void main(String[] args){ System.out.println(test()); } } /** * output: First Second */
- 異常丟失 :異常丟失指的是前一個異常還沒處理時就丟擲下一個異常,導致前一個異常沒有被處理,這是一個嚴重的程式設計錯誤。以下為參考程式碼。
public class Test { public static void a() throws AException{ throw new AException(); } public static void b() throws BException{ throw new BException(); } public static void main(String[] args){ try { try { a(); }finally { b(); } }catch (Exception ex){ System.out.println(ex); } } } class AException extends Exception{ @Override public String toString() { return "AException"; } } class BException extends Exception{ @Override public String toString() { return "BException"; } } /** * output: BException * * Analysis:根據以上輸出,因為a()已經執行了,且AException已經丟擲,而因為finally必定執行,導致新的BException被丟擲,被catch抓住,進而AException丟失。 */
結語:面試前,本來我是完完全全認為異常不重要的,只瞭解一些基礎的編譯期異常和執行時異常的區別。但這個面試給我了一個徹徹底底的下馬威,因為異常是這次面試中非常被重視的一個模組。
講道理,finally
那塊的程式碼結果分析直接把我問倒了,因此對於這個問題我都整個重新過了一遍。