- Java異常面試題(精心整理),你準備好接招了嗎?求怕累!
- 内容最後附帶阿裡巴巴開發手冊,整合不易,還請大家認真閱讀,并一鍵三連加關注,給部落客一些支援,謝謝了!筆芯 ❤
文章目錄
- Java異常架構與異常關鍵字
-
- 一. Java異常簡介
- 二. Java異常架構
-
- 1. Throwable
- 2. Error(錯誤)
- 3. Exception(異常)
- 4. 受檢異常與非受檢異常
- 三. Java異常關鍵字
- Java異常處理
-
- 一. 聲明異常
- 二. 抛出異常
- 三. 捕獲異常
- 四. 如何選擇異常類型
- 五. 常見異常處理方式
-
- 1. 直接抛出異常
- 2. 封裝異常再抛出
- 3. 捕獲異常
- 4. 自定義異常
- 5. try-catch-finally
- 6. try-with-resource
- Java異常常見面試題
-
- 一. Error 和 Exception 差別是什麼?
- 二. 運作時異常和一般異常(受檢異常)差別是什麼?
- 三. JVM 是如何處理異常的?
- 四. throw 和 throws 的差別是什麼?
- 五. final、finally、finalize 有什麼差別?
- 六. NoClassDefFoundError 和 ClassNotFoundException 差別?
- 七. try-catch-finally 中哪個部分可以省略?
- 八. try-catch-finally 中,如果 catch 中 return 了,finally 還會執行嗎?
- 九. 類 ExampleA 繼承 Exception,類 ExampleB 繼ExampleA。
- 十. 常見的 RuntimeException 有哪些?
- Java常見異常有哪些
- Java異常處理最佳實踐
-
- 一. 在 finally 塊中清理資源或者使用 try-with-resource 語句
-
- 1.使用 finally 代碼塊
- 2. Java 7 的 try-with-resource 語
- 二. 優先明确的異常
- 三. 對異常進行文檔說明
- 四. 使用描述性消息抛出異常
- 五. 優先捕獲最具體的異常
- 六. 不要捕獲 Throwable 類
- 七. 不要忽略異常
- 八. 不要記錄并抛出異常
- 九. 包裝異常時不要抛棄原始的異常
- 十. 不要使用異常控制程式的流程
- 十一. 使用标準異常
- 十二. 異常會影響性能
- 十三. 總結
- 異常處理-阿裡巴巴Java開發手冊
- Java異常題庫
-
- 一. 填空題(補全___上的内容)
- 二. 選擇題
- 三. 簡答題 (答案請在文章中尋找)
- 四. 編碼題
- 五. 可選題
- 六. 看代碼,判問題
Java異常架構與異常關鍵字
一. Java異常簡介
-
Java異常是Java提供的一種識别及響應錯誤的一緻性機制。
- Java異常機制可以使程式中異常處理代碼和正常業務代碼分離,保證程式代碼更加優雅,并提高程式健壯性。在有效使用異常的情況下,異常能清晰的回答what, where, why這3個問題:異常類型回答了“什麼”被抛出,異常堆棧跟蹤回答了“在哪”抛出,異常資訊回答了“為什麼”會抛出。
二. Java異常架構
1. Throwable
- Throwable 是 Java 語言中所有錯誤與異常的超類(父類)
- Throwable 包含兩個子類:Error(錯誤)和 Exception(異常),它們通常用于訓示發生了異常情況。
- Throwable 包含了其線程建立時線程執行堆棧的快照,它提供了 printStackTrace() 等接口用于擷取堆棧跟蹤資料等資訊。
2. Error(錯誤)
- 定義:Error 類及其子類。程式中無法處理的錯誤,表示運作應用程式中出現了嚴重的錯誤。
- 特點:此類錯誤一般表示代碼運作時 JVM 出現問題。通常有 Virtual MachineError(虛拟機運作錯誤)、NoClassDefFoundError(類定義錯誤)等。比如 OutOfMemoryError:記憶體不足錯誤;StackOverflowError:棧溢出錯誤。此類錯誤發生時,JVM 将終止線程。
- 這些錯誤是不受檢異常,非代碼性錯誤。是以,當此類錯誤發生時,應用程式不應該去處理此類錯誤。按照Java慣例,我們是不應該實作任何新的Error子類的!
3. Exception(異常)
- 程式本身可以捕獲并且可以處理的異常。 Exception 這種異常又分為兩類:運作時異常和編譯時異常。
運作時異常
- 定義: RuntimeException 類及其子類,表示 JVM 在運作期間可能出現的異常。
- 特點: Java 編譯器不會檢查它。也就是說,當程式中可能出現這類異常時,倘若既"沒有通過throws聲明抛出它",也"沒有用try-catch語句捕獲它",還是會編譯通過。比如NullPointerException空指針異常、ArrayIndexOutBoundException數組下标越界異常、ClassCastException類型轉換異常、ArithmeticExecption算術異常。此類異常屬于不受檢異常,一般是由程式邏輯錯誤引起的,在程式中可以選擇捕獲處理,也可以不處理。雖然 Java 編譯器不會檢查運作時異常,但是我們也可以通過 throws 進行聲明抛出,也可以通過 try-catch 對它進行捕獲處理。如果産生運作時異常,則需要通過修改代碼來進行避免。例如,若會發生除數為零的情況,則需要通過代碼避免該情況的發生!
- RuntimeException 異常會由 Java 虛拟機自動抛出并自動捕獲 (就算我們沒寫異常捕獲語句運作時也會抛出錯誤!!) ,此類異常的出現絕大數情況是代碼本身有問題應該從邏輯上去解決并改進代碼。
編譯時異常
- 定義: Exception 中除 RuntimeException 及其子類之外的異常。
- 特點: Java 編譯器會檢查它。如果程式中出現此類異常,比如 ClassNotFoundException(沒有找到指定的類異常),IOException(IO流異常),要麼通過throws進行聲明抛出,要麼通過trycatch進行捕獲處理,否則不能通過編譯。在程式中,通常不會自定義該類異常,而是直接使用系統提供的異常類。該異常我們必須手動在代碼裡添加捕獲語句來處理該異常。
4. 受檢異常與非受檢異常
- Java 的所有異常可以分為受檢異常(checked exception)和非受檢異常(unchecked exception)。
受檢異常
- 編譯器要求必須處理的異常。正确的程式在運作過程中,經常容易出現的、符合預期的異常情況。一旦發生此類異常,就必須采用某種方式進行處理。除 RuntimeException 及其子類外,其他的Exception 異常都屬于受檢異常。 編譯器會檢查此類異常,也就是說當編譯器檢查到應用中的某處可能會此類異常時,将會提示你處理本異常——要麼使用try-catch捕獲,要麼使用方法簽名中用 throws 關鍵字抛出,否則編譯不通過。
非受檢異常
- 編譯器不會進行檢查并且不要求必須處理的異常,也就說當程式中出現此類異常時,即使我們沒有try-catch捕獲它,也沒有使用throws抛出該異常,編譯也會正常通過。該類異常包括運作時異常(RuntimeException極其子類)和錯誤(Error)。
三. Java異常關鍵字
- try : 用于監聽。将要被監聽的代碼(可能抛出異常的代碼)放在try語句塊之内,當try語句塊内發生異常時,異常就被抛出。
- catch : 用于捕獲異常。catch用來捕獲try語句塊中發生的異常。
- finally : finally語句塊總是會被執行。它主要用于回收在try塊裡打開的物力資源(如資料庫連接配接、網絡連接配接和磁盤檔案)。隻有finally塊,執行完成之後,才會回來執行try或者catch塊中的return或者throw語句,如果finally中使用了return或者throw等終止方法的語句,則就不會跳回執行,直接停止。
- throw : 用于抛出異常。
- throws : 用在方法簽名中,用于聲明該方法可能抛出的異常。
Java異常處理
一. 聲明異常
- 通常,應該捕獲那些知道如何處理的異常,将不知道如何處理的異常繼續傳遞下去。傳遞異常可以在方法簽名處使用 throws 關鍵字聲明可能會抛出的異常。
-
注意:非檢查異常(Error、RuntimeException 或它們的子類)不可使用 throws 關鍵字來聲明要抛出的異常。 一個方法出現編譯時異常,就需要 try-catch/ throws 處理,否則會導緻編譯錯誤。
二. 抛出異常
- 如果你覺得解決不了某些異常問題,且不需要調用者處理,那麼你可以抛出異常。
- throw關鍵字作用是在方法内部抛出一個 Throwable 類型的異常。任何Java代碼都可以通過 throw 語句抛出異常。
三. 捕獲異常
- 程式通常在運作之前不報錯,但是運作後可能會出現某些未知的錯誤,但是還不想直接抛出到上一級,那麼就需要通過try…catch…的形式進行異常捕獲,之後根據不同的異常情況來進行相應的處理。
四. 如何選擇異常類型
- 可以根據下圖來選擇是捕獲異常,聲明異常還是抛出異常。
五. 常見異常處理方式
1. 直接抛出異常
- 通常,應該捕獲那些知道如何處理的異常,将不知道如何處理的異常繼續傳遞下去。傳遞異常可以在方法簽名處使用 throws 關鍵字聲明可能會抛出的異常。
private static void readFile(String filePath) throws IOException {
File file = new File(filePath);
String result;
BufferedReader reader = new BufferedReader(new FileReader(file));
while((result = reader.readLine())!=null) {
System.out.println(result);
}
reader.close();
}
2. 封裝異常再抛出
- 有時我們會從 catch 中抛出一個異常,目的是為了改變異常的類型。多用于在多系統內建時,當某個子系統故障,異常類型可能有多種,可以用統一的異常類型向外暴露,不需暴露太多内部異常細節。
private static void readFile(String filePath) throws MyException {
try {
// code
} catch (IOException e) {
MyException ex = new MyException("read file failed.");
ex.initCause(e);
throw ex;
}
}
3. 捕獲異常
- 在一個 try-catch 語句塊中可以捕獲多個異常類型,并對不同類型的異常做出不同的處理
private static void readFile(String filePath) {
try {
// code
} catch (FileNotFoundException e) {
// handle FileNotFoundException
} catch (IOException e){
// handle IOException
}
}
- 同一個 catch 也可以捕獲多種類型異常,用 | 隔開
private static void readFile(String filePath) {
try {
// code
} catch (FileNotFoundException | UnknownHostException e) {
// handle FileNotFoundException or UnknownHostException
} catch (IOException e){
// handle IOException
}
}
4. 自定義異常
- 習慣上,定義一個異常類應包含兩個構造函數,一個無參構造函數和一個帶有較長的描述資訊的構造函數(Throwable 的 toString 方法會列印這些詳細資訊,調試時很有用)
public class MyException extends Exception {
public MyException(){
}
public MyException(String msg){
super(msg);
}
// ...
}
5. try-catch-finally
- 當方法中發生異常,異常處之後的代碼不會再執行,如果之前擷取了一些本地資源需要釋放,則需要在方法正常結束時和 catch 語句中都調用釋放本地資源的代碼,顯得代碼比較繁瑣,finally 語句可以解決這個問題。
private static void readFile(String filePath) throws MyException {
File file = new File(filePath);
String result;
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(file));
while((result = reader.readLine())!=null) {
System.out.println(result);
}
} catch (IOException e) {
System.out.println("readFile method catch block.");
MyException ex = new MyException("read file failed.");
ex.initCause(e);
throw ex;
} finally {
System.out.println("readFile method finally block.");
if (null != reader) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 調用該方法時,讀取檔案時若發生異常,代碼會進入 catch 代碼塊,之後進入 finally 代碼塊;若讀取檔案時未發生異常,則會跳過 catch 代碼塊直接進入 finally 代碼塊。是以無論代碼中是否發生異常,fianlly 中的代碼都會執行。
- 若 catch 代碼塊中包含 return 語句,finally 中的代碼還會執行嗎?将以上代碼中的 catch 子句修改如下:
catch (IOException e) {
System.out.println("readFile method catch block.");
return;
}
- 調用 readFile 方法,觀察當 catch 子句中調用 return 語句時,finally 子句是否執行
readFile method catch block.
readFile method finally block.
- 可見,即使 catch 中包含了 return 語句,finally 子句依然會執行。若 finally 中也包含 return 語句,finally 中的 return 會覆寫前面的 return。
6. try-with-resource
- 上面例子中,finally 中的 close 方法也可能抛出 IOException, 進而覆寫了原始異常。JAVA 7 提供了更優雅的方式來實作資源的自動釋放,自動釋放的資源需要是實作了 AutoCloseable 接口的類。
private static void tryWithResourceTest(){
try (Scanner scanner = new Scanner(new FileInputStream("c:/abc"),"UTF-8")){
// code
} catch (IOException e){
// handle exception
}
}
- try 代碼塊退出時,會自動調用 scanner.close 方法,和把 scanner.close 方法放在 finally 代碼塊中不同的是,若 scanner.close 抛出異常,則會被抑制,抛出的仍然為原始異常。被抑制的異常會由addSusppressed 方法添加到原來的異常,如果想要擷取被抑制的異常清單,可以調用getSuppressed 方法來擷取。
Java異常常見面試題
一. Error 和 Exception 差別是什麼?
- Error 類型的錯誤通常為虛拟機相關錯誤,如系統崩潰,記憶體不足,堆棧溢出等,編譯器不會對這類錯誤進行檢測,JAVA 應用程式也不應對這類錯誤進行捕獲,一旦這類錯誤發生,通常應用程式會被終止,僅靠應用程式本身無法恢複;
- Exception 類的錯誤是可以在應用程式中進行捕獲并處理的,通常遇到這種錯誤,應對其進行處理,使應用程式可以繼續正常運作。
二. 運作時異常和一般異常(受檢異常)差別是什麼?
- 運作時異常包括 RuntimeException 類及其子類,表示 JVM 在運作期間可能出現的異常。 Java 編譯器不會檢查運作時異常。
- 受檢異常是Exception 中除 RuntimeException 及其子類之外的異常。 Java 編譯器會檢查受檢異常。
- RuntimeException異常和受檢異常之間的差別:是否強制要求調用者必須處理此異常,如果強制要求調用者必須進行處理,那麼就使用受檢異常,否則就選擇非受檢異常(RuntimeException)。一般來講,如果沒有特殊的要求,我們建議使用RuntimeException異常。
三. JVM 是如何處理異常的?
- 在一個方法中如果發生異常,這個方法會建立一個異常對象,并轉交給 JVM,該異常對象包含異常名稱,異常描述以及異常發生時應用程式的狀态。建立異常對象并轉交給 JVM 的過程稱為抛出異常。可能有一系列的方法調用,最終才進入抛出異常的方法,這一系列方法調用的有序清單叫做調用棧。
- JVM 會順着調用棧去查找看是否有可以處理異常的代碼,如果有,則調用異常處理代碼。當 JVM發現可以處理異常的代碼時,會把發生的異常傳遞給它。如果 JVM 沒有找到可以處理該異常的代碼塊,JVM 就會将該異常轉交給預設的異常處理器(預設處理器為 JVM 的一部分),預設異常處理器列印出異常資訊并終止應用程式。
四. throw 和 throws 的差別是什麼?
- Java 中的異常處理除了包括捕獲異常和處理異常之外,還包括聲明異常和拋出異常,可以通過 throws 關鍵字在方法上聲明該方法要拋出的異常,或者在方法内部通過 throw 拋出異常對象。
throws 關鍵字和 throw 關鍵字在使用上的幾點差別如下:
- throw 關鍵字用在方法内部,隻能用于抛出一種異常,用來抛出方法或代碼塊中的異常,受查異常和非受查異常都可以被抛出。
- throws 關鍵字用在方法聲明上,可以抛出多個異常,用來辨別該方法可能抛出的異常清單。一個方法用 throws 辨別了可能抛出的異常清單,調用該方法的方法中必須包含可處理異常的代碼,否則也要在方法簽名中用 throws 關鍵字聲明相應的異常。
五. final、finally、finalize 有什麼差別?
- final可以修飾類、變量、方法,修飾類表示該類不能被繼承、修飾方法表示該方法不能被重寫、修飾變量表示該變量是一個常量不能被重新指派。
- finally一般作用在try-catch代碼塊中,在處理異常的時候,通常我們将一定要執行的代碼方法finally代碼塊中,表示不管是否出現異常,該代碼塊都會執行,一般用來存放一些關閉資源的代碼。
- finalize是一個方法,屬于Object類的一個方法,而Object類是所有類的父類,Java 中允許使用finalize()方法在垃圾收集器将對象從記憶體中清除出去之前做必要的清理工作。
六. NoClassDefFoundError 和 ClassNotFoundException 差別?
- NoClassDefFoundError 是一個 Error 類型的異常,是由 JVM 引起的,不應該嘗試捕獲這個異常。
- 引起該異常的原因是 JVM 或 ClassLoader 嘗試加載某類時在記憶體中找不到該類的定義,該動作發生在運作期間,即編譯時該類存在,但是在運作時卻找不到了,可能是變異後被删除了等原因導緻;
-
ClassNotFoundException 是一個受查異常,需要顯式地使用 try-catch 對其進行捕獲和處理,或在方法簽名中用 throws 關鍵字進行聲明。當使用 Class.forName, ClassLoader.loadClass 或
ClassLoader.findSystemClass 動态加載類到記憶體的時候,通過傳入的類路徑參數沒有找到該類,就會抛出該異常;另一種抛出該異常的可能原因是某個類已經由一個類加載器加載至記憶體中,另一個加載器又嘗試去加載它。
七. try-catch-finally 中哪個部分可以省略?
- 答:catch 可以省略
原因
- 更為嚴格的說法其實是:try隻适合處理運作時異常,try+catch适合處理運作時異常+普通異常。也就是說,如果你隻用try去處理普通異常卻不加以catch處理,編譯是通不過的,因為編譯器硬性規定,普通異常如果選擇捕獲,則必須用catch顯示聲明以便進一步處理。而運作時異常在編譯時沒有如此規定,是以catch可以省略,你加上catch編譯器也覺得無可厚非。
- 理論上,編譯器看任何代碼都不順眼,都覺得可能有潛在的問題,是以你即使對所有代碼加上try,代碼在運作期時也隻不過是在正常運作的基礎上加一層皮。但是你一旦對一段代碼加上try,就等于顯示地承諾編譯器,對這段代碼可能抛出的異常進行捕獲而非向上抛出處理。如果是普通異常,編譯器要求必須用catch捕獲以便進一步處理;如果運作時異常,捕獲然後丢棄并且+finally掃尾處理,或者加上catch捕獲以便進一步處理。
- 至于加上finally,則是在不管有沒捕獲異常,都要進行的“掃尾”處理。
八. try-catch-finally 中,如果 catch 中 return 了,finally 還會執行嗎?
- 答: 會執行,在 return 前執行。
- 注意: 在 finally 中改變傳回值的做法是不好的,因為如果存在 finally 代碼塊,try中的 return 語句不會立馬傳回調用者,而是記錄下傳回值待 finally 代碼塊執行完畢之後再向調用者傳回其值,然後如果在finally 中修改了傳回值,就會傳回修改後的值。顯然,在 finally 中傳回或者修改傳回值會對程式造成很大的困擾,C#中直接用編譯錯誤的方式來阻止程式員幹這種龌龊的事情,Java中也可以通過提升編譯器的文法檢查級别來産生警告或錯誤。
代碼示例1:
public static int getInt() {
int a = 10;
try {
System.out.println(a / 0);
a = 20;
} catch (ArithmeticException e) {
a = 30;
return a;
/*
* return a 在程式執行到這一步的時候,這裡不是return a 而是 return 30;這個傳回路徑就形成了
* 但是呢,它發現後面還有finally,是以繼續執行finally的内容,a=40
* 再次回到以前的路徑,繼續走return 30,形成傳回路徑之後,這裡的a就不是a變量了,而是常量30
*/
} finally {
a = 40;
}
return a;
}
-
執行結果:30
代碼示例2:
public static int getInt() {
int a = 10;
try {
System.out.println(a / 0);
a = 20;
} catch (ArithmeticException e) {
a = 30;
return a;
} finally {
a = 40;
//如果這樣,就又重新形成了一條傳回路徑,由于隻能通過1個return傳回,是以這裡直接傳回40
return a;
}
}
-
執行結果:40
九. 類 ExampleA 繼承 Exception,類 ExampleB 繼ExampleA。
- 有如下代碼片斷:
try {
throw new ExampleB("b")
} catch(ExampleA e){
System.out.println("ExampleA");
} catch(Exception e){
System.out.println("Exception");
}
- 請問執行此段代碼的輸出是什麼?
- 答:
(根據裡氏代換原則[能使用父類型的地方一定能使用子類型],抓取ExampleA 類型異常的 catch 塊能夠抓住 try 塊中抛出的 ExampleB 類型的異常)輸出:ExampleA。
十. 常見的 RuntimeException 有哪些?
- ClassCastException(類轉換異常)
- IndexOutOfBoundsException(數組越界)
- NullPointerException(空指針)
- ArrayStoreException(資料存儲異常,操作數組時類型不一緻)
- 還有IO操作的BufferOverflowException異常
Java常見異常有哪些
- java.lang.IllegalAccessError:違法通路錯誤。當一個應用試圖通路、修改某個類的域(Field)或者調用其方法,但是又違反域或方法的可見性聲明,則抛出該異常。
- java.lang.InstantiationError:執行個體化錯誤。當一個應用試圖通過Java的new操作符構造一個抽象類或者接口時抛出該異常.
- java.lang.OutOfMemoryError:記憶體不足錯誤。當可用記憶體不足以讓Java虛拟機配置設定給一個對象時抛出該錯誤。
- java.lang.StackOverflowError:堆棧溢出錯誤。當一個應用遞歸調用的層次太深而導緻堆棧溢出或者陷入死循環時抛出該錯誤。
- java.lang.ClassCastException:類造型異常。假設有類A和B(A不是B的父類或子類),O是A的執行個體,那麼當強制将O構造為類B的執行個體時抛出該異常。該異常經常被稱為強制類型轉換異常。
- java.lang.ClassNotFoundException:找不到類異常。當應用試圖根據字元串形式的類名構造類,而在周遊CLASSPAH之後找不到對應名稱的class檔案時,抛出該異常。
- java.lang.ArithmeticException:算術條件異常。譬如:整數除零等。
- java.lang.ArrayIndexOutOfBoundsException:數組索引越界異常。當對數組的索引值為負數或大于等于數組大小時抛出。
- java.lang.IndexOutOfBoundsException:索引越界異常。當通路某個序列的索引值小于0或大于等于序列大小時,抛出該異常。
- java.lang.InstantiationException:執行個體化異常。當試圖通過newInstance()方法建立某個類的執行個體,而該類是一個抽象類或接口時,抛出該異常。
- java.lang.NoSuchFieldException:屬性不存在異常。當通路某個類的不存在的屬性時抛出該異常。
- java.lang.NoSuchMethodException:方法不存在異常。當通路某個類的不存在的方法時抛出該異常。
- java.lang.NullPointerException:空指針異常。當應用試圖在要求使用對象的地方使用了null時,抛出該異常。
- java.lang.NumberFormatException:數字格式異常。當試圖将一個String轉換為指定的數字類型,而該字元串确不滿足數字類型要求的格式時,抛出該異常。
- java.lang.StringIndexOutOfBoundsException:字元串索引越界異常。當使用索引值通路某個字元串中的字元,而該索引值小于0或大于等于序列大小時,抛出該異常。
Java異常處理最佳實踐
在 Java 中處理異常并不是一個簡單的事情。不僅僅初學者很難了解,即使一些有經驗的開發者也需要花費很多時間來思考如何處理異常,包括需要處理哪些異常,怎樣處理等等。這也是絕大多數開發團隊都會制定一些規則來規範進行異常處理的原因。而團隊之間的這些規範往往是截然不同的。
- 本文給出幾個被很多團隊使用的異常處理最佳實踐。
一. 在 finally 塊中清理資源或者使用 try-with-resource 語句
- 當使用類似InputStream這種需要使用後關閉的資源時,一個常見的錯誤就是在try塊的最後關閉資源。
public void doNotCloseResourceInTry() {
FileInputStream inputStream = null;
try {
File file = new File("./tmp.txt");
inputStream = new FileInputStream(file);
// use the inputStream to read a file
// do NOT do this
inputStream.close();
} catch (FileNotFoundException e) {
log.error(e);
} catch (IOException e) {
log.error(e);
}
}
- 問題就是,隻有沒有異常抛出的時候,這段代碼才可以正常工作。try 代碼塊内代碼會正常執行,并且資源可以正常關閉。但是,使用 try 代碼塊是有原因的,一般調用一個或多個可能抛出異常的方法,而且,你自己也可能會抛出一個異常,這意味着代碼可能不會執行到 try 代碼塊的最後部分。結果就是,你并沒有關閉資源。
是以,你應該把清理工作的代碼放到 finally 裡去,或者使用 try-with-resource 特性。
1.使用 finally 代碼塊
- 與前面幾行 try 代碼塊不同,finally 代碼塊總是會被執行。不管 try 代碼塊成功執行之後還是你在catch 代碼塊中處理完異常後都會執行。是以,你可以確定你清理了所有打開的資源。
public void closeResourceInFinally() {
FileInputStream inputStream = null;
try {
File file = new File("./tmp.txt");
inputStream = new FileInputStream(file);
// use the inputStream to read a file
} catch (FileNotFoundException e) {
log.error(e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
log.error(e);
}
}
}
}
2. Java 7 的 try-with-resource 語
- 如果你的資源實作了 AutoCloseable 接口,你可以使用這個文法。大多數的 Java 标準資源都繼承了這個接口。當你在 try 子句中打開資源,資源會在 try 代碼塊執行後或異常處理後自動關閉。
public void automaticallyCloseResource() {
File file = new File("./tmp.txt");
try (FileInputStream inputStream = new FileInputStream(file);) {
// use the inputStream to read a file
} catch (FileNotFoundException e) {
log.error(e);
} catch (IOException e) {
log.error(e);
}
}
二. 優先明确的異常
- 你抛出的異常越明确越好,永遠記住,你的同僚或者幾個月之後的你,将會調用你的方法并且處理異常。
- 是以需要保證提供給他們盡可能多的資訊。這樣你的 API 更容易被了解。你的方法的調用者能夠更好的處理異常并且避免額外的檢查。
- 是以,總是嘗試尋找最适合你的異常事件的類,例如,抛出一個 NumberFormatException 來替換一個 IllegalArgumentException 。避免抛出一個不明确的異常。
public void doNotDoThis() throws Exception {
...
}
public void doThis() throws NumberFormatException {
...
}
三. 對異常進行文檔說明
- 當在方法上聲明抛出異常時,也需要進行文檔說明。目的是為了給調用者提供盡可能多的資訊,進而可以更好地避免或處理異常。在 Javadoc 添加 @throws 聲明,并且描述抛出異常的場景。
public void doSomething(String input) throws MyBusinessException {
...
}
四. 使用描述性消息抛出異常
- 在抛出異常時,需要盡可能精确地描述問題和相關資訊,這樣無論是列印到日志中還是在監控工具中,都能夠更容易被人閱讀,進而可以更好地定位具體錯誤資訊、錯誤的嚴重程度等。
- 但這裡并不是說要對錯誤資訊長篇大論,因為本來 Exception 的類名就能夠反映錯誤的原因,是以隻需要用一到兩句話描述即可。
- 如果抛出一個特定的異常,它的類名很可能已經描述了這種錯誤。是以,你不需要提供很多額外的資訊。一個很好的例子是 NumberFormatException 。當你以錯誤的格式提供 String 時,它将被java.lang.Long 類的構造函數抛出。
try {
new Long("xyz");
} catch (NumberFormatException e) {
log.error(e);
}
五. 優先捕獲最具體的異常
- 大多數 IDE 都可以幫助你實作這個最佳實踐。當你嘗試首先捕獲較不具體的異常時,它們會報告無法通路的代碼塊。
- 但問題在于,隻有比對異常的第一個 catch 塊會被執行。 是以,如果首先捕獲IllegalArgumentException ,則永遠不會到達應該處理更具體的 NumberFormatException 的 catch 塊,因為它是 IllegalArgumentException 的子類。
- 總是優先捕獲最具體的異常類,并将不太具體的 catch 塊添加到清單的末尾。
- 你可以在下面的代碼片斷中看到這樣一個 try-catch 語句的例子。 第一個 catch 塊處理所有NumberFormatException 異常,第二個處理所有非 NumberFormatException 異常的IllegalArgumentException 異常。
public void catchMostSpecificExceptionFirst() {
try {
doSomething("A message");
} catch (NumberFormatException e) {
log.error(e);
} catch (IllegalArgumentException e) {
log.error(e)
}
}
六. 不要捕獲 Throwable 類
- Throwable 是所有異常和錯誤的超類。你可以在 catch 子句中使用它,但是你永遠不應該這樣做!
- 如果在 catch 子句中使用 Throwable ,它不僅會捕獲所有異常,也将捕獲所有的錯誤。JVM 抛出錯誤,指出不應該由應用程式處理的嚴重問題。 典型的例子是 OutOfMemoryError 或者 StackOverflowError 。兩者都是由應用程式控制之外的情況引起的,無法處理。
- 是以,最好不要捕獲 Throwable ,除非你确定自己處于一種特殊的情況下能夠處理錯誤。
public void doNotCatchThrowable() {
try {
// do something
} catch (Throwable t) {
// don't do this!
}
}
七. 不要忽略異常
- 很多時候,開發者很有自信不會抛出異常,是以寫了一個catch塊,但是沒有做任何處理或者記錄日志。
public void doNotIgnoreExceptions() {
try {
// do something
} catch (NumberFormatException e) {
// this will never happen
}
}
- 但現實是經常會出現無法預料的異常,或者無法确定這裡的代碼未來是不是會改動(删除了阻止異常抛出的代碼),而此時由于異常被捕獲,使得無法拿到足夠的錯誤資訊來定位問題。
- 合理的做法是至少要記錄異常的資訊。
public void logAnException() {
try {
// do something
} catch (NumberFormatException e) {
log.error("This should never happen: " + e);
}
}
八. 不要記錄并抛出異常
- 這可能是本文中最常被忽略的最佳實踐。可以發現很多代碼甚至類庫中都會有捕獲異常、記錄日志并再次抛出的邏輯。如下:
try {
new Long("mzc");
} catch (NumberFormatException e) {
log.error(e);
throw e;
}
- 這個處理邏輯看着是合理的。但這經常會給同一個異常輸出多條日志。如下:
17:44:28,945 ERROR TestExceptionHandling:65 - java.lang.NumberFormatException: For input string: "mzc"
Exception in thread "main" java.lang.NumberFormatException: For input string:
"xyz"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:589)
at java.lang.Long.(Long.java:965)
at
com.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHan
dling.java:63)
at
com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58)
- 如上所示,後面的日志也沒有附加更有用的資訊。如果想要提供更加有用的資訊,那麼可以将異常包裝為自定義異常。
public void wrapException(String input) throws MyBusinessException {
try {
// do something
} catch (NumberFormatException e) {
throw new MyBusinessException("A message that describes the error.", e);
}
}
- 是以,僅僅當想要處理異常時才去捕獲,否則隻需要在方法簽名中聲明讓調用者去處理。
九. 包裝異常時不要抛棄原始的異常
- 捕獲标準異常并包裝為自定義異常是一個很常見的做法。這樣可以添加更為具體的異常資訊并能夠做針對的異常處理。
- 在你這樣做時,請確定将原始異常設定為原因(注:參考下方代碼 NumberFormatException e 中的原始異常 e )。Exception 類提供了特殊的構造函數方法,它接受一個 Throwable 作為參數。否則,你将會丢失堆棧跟蹤和原始異常的消息,這将會使分析導緻異常的異常事件變得困難。
public void wrapException(String input) throws MyBusinessException {
try {
// do something
} catch (NumberFormatException e) {
throw new MyBusinessException("A message that describes the error.", e);
}
}
十. 不要使用異常控制程式的流程
- 不應該使用異常控制應用的執行流程,例如,本應該使用if語句進行條件判斷的情況下,你卻使用異常處理,這是非常不好的習慣,會嚴重影響應用的性能。
十一. 使用标準異常
- 如果使用内建的異常可以解決問題,就不要定義自己的異常。Java API 提供了上百種針對不同情況的異常類型,在開發中首先盡可能使用 Java API 提供的異常,如果标準的異常不能滿足你的要求,這時候建立自己的定制異常。盡可能得使用标準異常有利于新加入的開發者看懂項目代碼。
十二. 異常會影響性能
- 異常處理的性能成本非常高,每個 Java 程式員在開發時都應牢記這句話。建立一個異常非常慢,抛出一個異常又會消耗1~5ms,當一個異常在應用的多個層級之間傳遞時,會拖累整個應用的性能。
- 僅在異常情況下使用異常;
- 在可恢複的異常情況下使用異常;
- 盡管使用異常有利于 Java 開發,但是在應用中最好不要捕獲太多的調用棧,因為在很多情況下都不需要列印調用棧就知道哪裡出錯了。是以,異常消息應該提供恰到好處的資訊。
十三. 總結
- 綜上所述,當你抛出或捕獲異常的時候,有很多不同的情況需要考慮,而且大部分事情都是為了改善代碼的可讀性或者 API 的可用性。
- 異常不僅僅是一個錯誤控制機制,也是一個通信媒介。是以,為了和同僚更好的合作,一個團隊必須要制定出一個最佳實踐和規則,隻有這樣,團隊成員才能了解這些通用概念,同時在工作中使用它。
異常處理-阿裡巴巴Java開發手冊
線上版位址:《阿裡巴巴開發手冊終極版1.3.0》
Java異常題庫
一. 填空題(補全___上的内容)
- ____機制是一種非常有用的輔助性程式設計方法。采用這種方法可以使得在程式設計時将程式的正常流程與錯誤處理分開,有利于代碼的編寫和維護。
- 在Java異常進行中可以使用多個catch子句,此時包含異常類的父類Exception的catch子句的位置應該是在____。
- 異常進行中finally塊可確定無論是否發生異常,該塊中代碼總能被執行。finally塊不執行的唯一情況是在異常處理代碼中執行____語句退出Java虛拟機。
- 異常是由Java應用程式抛出和處理的非嚴重錯誤,比如所需檔案沒有找到、零作除數,數組下标越界等,可分為兩類:Checked異常和_____。
- 在Java中對于程式可能出現的檢查時異常,要麼用try…catch語句捕獲并處理它,要麼使用___語句抛出它,由上一級調用者來處理。
- Java異常進行中,如果一個方法中出現了多個Checked異常,可以在方法聲明中使用關鍵字___聲明抛出,各異常類型之間使用逗号分隔。
答案
- 異常處理
- 最後
- System.exit()
- 運作時異常
- throws
- thorws
二. 選擇題
- 以下關于異常的代碼的執行結果是( )。(選擇一項)
public class Test {
public static void main(String args[]) {
try {
System.out.println("try");
return;
} catch(Exception e){
System.out.println("catch");
}finally {
System.out.println("finally");
}
}
}
- A.try catch finally
- B.catch finally
- C.try finally
- D.Try
- 在異常進行中,如釋放資源、關閉檔案等由( )來完成。(選擇一項)
- A.try子句
- B.`catch子句
- C.finally子句
- D.throw子句
- 編譯并運作如下Java程式,将輸出( )。(選擇一項)
public static void main(String[] args) {
try {
int num1 = 2;
int num2 = 0;
int result = num1 / num2;
System.out.println(result);
throw new NumberFormatException( );
} catch (ArrayIndexOutOfBoundsException e) {
System.out.print("1");
} catch (NumberFormatException e) {
System.out.print("2");
} catch (Exception e) {
System.out.print("3");
} finally {
System.out.print("4");
}
System.out.print("5");
}
}
- A.134
- B.2345
- C.1345
- D.345
- 下面選項中有關Java異常處理模型的說法錯誤的是( )。(選擇二項)
- A.一個try塊隻能有一條catch語句
- B.一個try塊中可以不使用catch語句
- C.catch塊不能單獨使用,必須始終與try塊在一起
- D.finally塊可以單獨使用,不是必須與try塊在一起
- 下面選項中屬于運作時異常的是( )。(選擇二項)
- A. Exception和SexException
- B. NullPointerException和InputMismatchException
- C. ArithmeticException和ArrayIndexOutOfBoundsException
- D. ClassNotFoundException和ClassCastException
- 閱讀如下Java代碼,在控制台輸入"-1",執行結果是( )。(選擇一項)
public class Demo {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("請輸入數字:");
try {
int num = input.nextInt();
if (num < 1 || num > 4) {
throw new Exception("必須在1-4之間!");
}
} catch (InputMismatchException e) {
System.out.println("InputMismatchException");
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
- A. 輸出:InputMismatchException
- B. 輸出:必須在1-4之間!
- C. 什麼也沒輸出
- D. 編譯錯誤
答案
- C
- C
- D
- AD
- BC
- B
三. 簡答題 (答案請在文章中尋找)
- Error和Exception的差別
- Checked異常和Runtime異常的差別。
- Java異常進行中,關鍵字try、catch、finally、throw、throws分别代表什麼含義?
- throws和throw的差別
- java中的兩種異常類型是什麼?他們有什麼差別?
四. 編碼題
- 1.編寫程式接收使用者輸入分數資訊,如果分數在0—100之間,輸出成績。如果成績不在該範圍内,抛出異常資訊,提示分數必須在0—100之間。
-
要求:使用自定義異常實作
/**
*
* 分數範圍異常類型
*
* @author Administrator
*
*
*
*/
public class ScoreScopeException extends RuntimeException
{
public ScoreScopeException()
{
super();
}
public ScoreScopeException(String message)
{
super(message);
}
}
import java.util.Scanner;
public class Test
{
public static void main(String[] args)
{
Scanner scanner = new Scanner(System.in);
try
{
System.out.print("請輸入分數:");
int score = scanner.nextInt();
if (score < 0 || score > 100)
{
throw new ScoreScopeException("分數必須在0-100之間");
}
System.out.println("分數為:" + score);
} catch (ScoreScopeException e)
{
System.out.println(e.getMessage());
e.printStackTrace();
}
}
}
- 2.寫一個方法void isTriangle(int a,int b,int c),判斷三個參數是否能構成一個三角形, 如果不能則抛出異常IllegalArgumentException,顯示異常資訊 “a,b,c不能構成三角形”,如果可以構成則顯示三角形三個邊長,在主方法中得到指令行輸入的三個整數, 調用此方法,并捕獲異常。
public class Test
{
public static void main(String[] args)
{
//輸入三條邊
System.out.println("請輸入三條邊長");
Scanner reader = new Scanner(System.in);
int a = reader.nextInt();
int b = reader.nextInt();
int c = reader.nextInt();
//判斷是否是三角形
isTriangle(a, b, c);
}
private static void isTriangle(int a, int b, int c)
{
//三條邊都不能是負數
if (a < 0 || b < 0 || c < 0)
{
throw new IllegalArgumentException("三條邊不能是負數");
}
//判斷是否構成三角形
if (a + b > c && a + c > b && b + c > a)
{
System.out.println("三角形的邊長分别為" + "a=" + a + " "
+ "b=" + b + " " + "c=" + " " + c);
} else
{
throw new IllegalArgumentException(a + "," + b + "," + c
+ "不能構成三角形!");
}
}
}
五. 可選題
- 1.編寫一個計算N個整數平均值的程式。程式應該提示使用者輸入N的值,如何必須輸入所有N個數。如果使用者輸入的值是一個負數,則應該抛出一個異常并捕獲,提示“N必須是正數或者0”。并提示使用者再次輸入該數
public class Test
{
public static void main(String[] args)
{
Scanner input = new Scanner(System.in);
System.out.println("要計算幾個整數的平均值呢:");
int n = input.nextInt();
// 使用sum求出使用者輸入的數的總和;
int sum = 0;
int num = 0;
// 使用循環提醒使用者輸入
for (int i = 0; i < n; i++)
{
System.out.println("請輸入第" + (i + 1) + "個數");
try
{
num = input.nextInt();
if (num < 0)
{
// i<0抛出異常
throw new Exception("N必須是正數或者0");
}
sum += num;
} catch (Exception e)
{
// 使用遞歸,如果出現異常繼續輸入
System.out.println(e.getMessage());
i--;
}
}
System.out.println("一共" + n + "個數," + "和為:" + sum
+ ",平均值為:" + sum / n);
}
}
六. 看代碼,判問題
問題 1
public class Father{
public void test() throws IOException{
throw new IOException("IOException");
}
}
public class Son extends Father{
public void test throws Exception{
throw new Exception("Exception");
}
}
- 答:上面的代碼片段在編譯時子類方法會出現編譯異常,因為在 java 中重寫方法抛出的異常不能是原方法抛出異常的父類,這裡test方法在父類中抛出了 IOException,所有在子類中的方法隻能抛出IOExcepition 或是其子類。
問題 2
public int test1(){
int temp = 0;
try{
return temp;
}finally{
temp = 2;
}
}
public int test2(){
int temp = 0;
try{
int a = 5/0;
return temp;
}finally{
return 2;
}
}
public void test3(){
try{
int temp = 5/0;
}finally{
throw new RuntimeException("RuntimeException");
}
}
- 答:
- test1方法中傳回0,因為執行到 try 的 return temp; 語句前會先将傳回值 temp儲存在一個臨時變量中,然後才執行 finally 語句,最後 try 再傳回那個臨時變量,finally 中對 temp 的修改不會被傳回。
- test2 方法運作傳回 2,因為 5/0 會觸發 ArithmeticException 異常,但是 finally 中有 return 語句,finally 中 return 不僅會覆寫 try 和 catch 内的傳回值且還會掩蓋 try 和 catch 内的異常,就像異常沒有發生一樣(特别注意,當 finally 中沒有 return 時該方法運作會抛出 ArithmeticException 異常),是以這個方法就會傳回 2,而且不再向上傳遞異常了。
- test3方法會抛出RuntimeException異常,原異常會被覆寫。