Java中的異常處理是個不簡單的話題。初學者很難理解,即便是經驗豐富的開發人員也能夠花費數小時來討論如何以及應該拋出或處理哪些異常。java
這就是爲何大多數開發團隊都有一套關於如何使用它們的規則的緣由。並且,若是您是團隊新手,那麼您可能會感到驚訝,這些規則與您之前使用的規則有何不一樣。面試
儘管如此,大多數團隊仍是採用了幾種最佳實踐。如下是9個最重要的信息,它們能夠幫助您入門或改善異常處理。app
常常發生的是,您在try塊中使用了一個資源,例如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塊中的全部語句將被執行,而且資源將被關閉。ide
可是您添加try塊是有緣由的。您調用一個或多個可能引起異常的方法,或者您可能本身引起異常。這意味着您可能未到達try塊的末尾。所以,您將不會關閉資源。函數
所以,您應該將全部清理代碼放入finally塊中,或使用try-with-resource語句。工具
使用finally模塊this
與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); } } } }複製代碼
Java 7的Try-With-Resource語句另外一個選擇是try-with-resource語句,我在Java異常處理簡介中對其進行了詳細說明。code
若是您的資源實現了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更易於理解。結果,您的方法的調用者將可以更好地處理該異常,或者經過額外的check避免該異常。
所以,請始終嘗試查找最適合您的異常事件的類,例如,拋出NumberFormatException而不是.
IllegalArgumentException。並避免引起不肯定的Exception。 public void doNotDoThis() throws Exception { ... } public void doThis() throws NumberFormatException { ... }複製代碼
整理了一下2021年的Java工程師經典面試真題,共485頁大概850道含答案的面試題PDF,包含了Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、Redis、MySQL、Spring、Spring Boot、Spring Cloud、RabbitMQ、Kafka、Linux 等幾乎全部技術棧,每一個技術棧都有很多於50道經典面試真題,不敢說刷完包你進大廠,但有針對性的刷讓你面對面試官的時候多幾分底氣仍是沒問題的。
每當在方法簽名中指定異常時,也應在Javadoc中對其進行記錄。這與之前的最佳實踐具備相同的目標:爲呼叫者提供儘量多的信息,以便他能夠避免或處理異常。
所以,請確保在Javadoc中添加一個@throws聲明,並描述可能致使異常的狀況。
/** * This method does something extremely useful ... * * @param input * @throws MyBusinessException if ... happens */ public void doSomething(String input) throws MyBusinessException { ... }複製代碼
最佳實踐背後的想法與前兩個相似。可是這一次,您沒有將信息提供給您的方法的調用者。每一個必須瞭解該日誌文件或您的監視工具中報告該異常時發生的狀況的人均可以閱讀該異常的消息。
所以,它應儘量準確地描述問題,並提供最相關的信息以瞭解異常事件。
不要誤會個人意思;您不該該寫一段文字。可是您應該用1-2個簡短的句子來講明出現異常的緣由。這能夠幫助您的運營團隊瞭解問題的嚴重性,還可使您更輕鬆地分析任何服務事件。
若是拋出特定的異常,則其類名極可能已經描述了錯誤的種類。所以,您無需提供不少其餘信息。一個很好的例子是NumberFormatException。當您以錯誤的格式提供String時,它將由類java.lang.Long的構造函數引起。
try { new Long("xyz"); } catch (NumberFormatException e) { log.error(e); }複製代碼
NumberFormatException類的名稱已經告訴您問題的類型。它的消息僅須要提供引發問題的輸入字符串。若是異常類的名稱不那麼具備表現力,則須要在消息中提供所需的信息。
17:17:26,386 ERROR TestExceptionHandling:52 - java.lang.NumberFormatException: For input string: "xyz"
大多數IDE均可以幫助您得到最佳實踐。當您嘗試首先捕獲不太具體的異常時,它們報告沒法訪問的代碼塊。
問題在於僅執行與異常匹配的第一個catch塊。所以,若是您首先捕獲IllegalArgumentException,那麼您將永遠不會到達應該處理更具體的NumberFormatException的catch塊,由於它是IllegalArgumentException的子類。
始終首先捕獲最具體的異常類,並將不那麼具體的捕獲塊添加到列表的末尾。
您能夠在如下代碼片斷中看到這樣的try-catch語句的示例。第一個catch塊處理全部NumberFormatException,第二個全部IllegalArgumentException,它們不是NumberFormatException。
public void catchMostSpecificExceptionFirst() { try { doSomething("A message"); } catch (NumberFormatException e) { log.error(e); } catch (IllegalArgumentException e) { log.error(e) } }複製代碼
Throwable是全部異常和錯誤的超類。您能夠在catch子句中使用它,但絕對不要這樣作!
若是在catch子句中使用Throwable,它將不只捕獲全部異常,並且還捕獲全部Exception。它還會捕獲全部Error。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("xyz"); } catch (NumberFormatException e) { log.error(e); throw e; }複製代碼
記錄發生的異常,而後將其從新拋出,以便調用者能夠適當地處理它,這可能會很直觀。可是它將爲同一異常寫入多個錯誤消息。
17:44:28,945 ERROR TestExceptionHandling:65 - java.lang.NumberFormatException: For input string: "xyz" 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(TestExceptionHandling.java:63) at com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58) 在com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58)複製代碼
其餘消息也不會添加任何信息。如最佳作法4中所述,異常消息應描述異常事件。堆棧跟蹤會告訴您在哪一個類,方法和行中引起了異常。
若是須要添加其餘信息,則應捕獲異常並將其包裝在自定義異常中。可是請確保遵循最佳實踐9。
public void wrapException(String input) throws MyBusinessException { try { // do something } catch (NumberFormatException e) { throw new MyBusinessException("A message that describes the error.", e); } }複製代碼
所以,僅在要處理它時才捕獲異常。不然,請在方法簽名中指定它,而後讓調用者來處理它。
有時最好捕獲一個標準異常並將其包裝到自定義異常中。這種例外的典型示例是特定於應用程序或框架的業務例外。這使您能夠添加其餘信息,還能夠對異常類實施特殊處理。
執行此操做時,請確保將原始異常設置爲緣由。該異常類提供了接受一個特定的構造方法的Throwable做爲參數。不然,您將丟失堆棧跟蹤和原始異常的消息,這將使分析致使您的異常的異常事件變得困難。
public void wrapException(String input) throws MyBusinessException { try { // do something } catch (NumberFormatException e) { throw new MyBusinessException("A message that describes the error.", e); } }複製代碼
綜上所述,拋出或捕獲異常時,您應該考慮不少不一樣的事情。他們中大多數人的目標是提升代碼的可讀性或API的可用性。
異常一般是同時存在的錯誤處理機制和通訊介質。所以,您應該確保與同事討論要應用的最佳實踐和規則,以便每一個人都能理解通常概念並以相同的方式使用它們。