Java中的異常處理不是一個簡單的話題。初學者很難理解,甚至有經驗的開發(fā)人員也會花幾個小時來討論應該如何拋出或處理這些異常。
創(chuàng)新互聯專注于企業(yè)全網營銷推廣、網站重做改版、本溪網站定制設計、自適應品牌網站建設、H5高端網站建設、商城網站制作、集團公司官網建設、外貿網站建設、高端網站制作、響應式網頁設計等建站業(yè)務,價格優(yōu)惠性價比高,為本溪等各大城市提供網站開發(fā)制作服務。
這就是為什么大多數開發(fā)團隊都有自己的異常處理的規(guī)則和方法。如果你是一個團隊的新手,你可能會驚訝于這些方法與你之前使用過的那些方法有多么不同。
然而,有幾種異常處理的最佳方法被大多數開發(fā)團隊所使用。下面是幫助改進異常處理的9個最重要的方法。
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內的所有語句都將被執(zhí)行,資源也會被關閉。
但是你在try里調用了一個或多個可能拋出異常的方法,或者自己拋出異常。這意味著可能無法到達try的末尾。因此,將不會關閉這些資源。
所以應該將清理資源的代碼放入Finally中,或者使用Try-With-Resource語句。
使用Finally
相比于try,無論是在成功執(zhí)行try里的代碼后,或是在catch中處理了一個異常后,Finally里的內容是一定會被執(zhí)行的。因此,可以確保清理所有已打開的資源。
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語句,在 introduction to Java exception handling 中更詳細地說明了這一點。
如果你的資源實現了 AutoCloseable 接口,就可以使用它,這正是大多數Java標準資源所做的。當你在try子句中打開資源時,它將在try被執(zhí)行后自動關閉,或者處理一個異常。
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 聲明,并描述可能導致的異常情況。
/**
因此,應該盡可能準確地描述問題,并提供相關的信息來了解異常事件。
別誤會,你不需要寫一段文字,而是應該用1-2個簡短的句子解釋異常的原因。這可以幫助開發(fā)團隊理解問題的嚴重性,同時也使你能夠更容易地分析任何服務事件。
如果拋出一個特定的異常,它的類名很可能已經描述了這種類型的錯誤。所以,你不需要提供很多額外的信息。一個很好的例子就是,當你以錯誤的格式使用字符串時,如NumberFormatException,它就會被類 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"
問題是只有第一個匹配到異常的catch語句才會被執(zhí)行,所以,如果你最先發(fā)現IllegalArgumentException,你將永遠不會到達catch里處理更具體的NumberFormatException,因為它是IllegalArgumentException的一個子類。
所以要首先捕獲特定的異常類,并在末尾添加一些處理不是很具體異常的catch語句。
你可以在下面的代碼片段中看到這樣一個try-catch語句的示例。第一個catch處理所有NumberFormatExceptions異常,第二個catch 處理NumberFormatException異常以外的illegalargumentexception異常。
public void catchMostSpecificExceptionFirst() {
try {
doSomething("A message");
} catch (NumberFormatException e) {
log.error(e);
} catch (IllegalArgumentException e) {
log.error(e)
}
}
如果你在catch子句中使用Throwable,它將不僅捕獲所有的異常,還會捕獲所有錯誤。JVM會拋出錯誤,這是應用程序不打算處理的嚴重問題。典型的例子是 OutOfMemoryError 或 StackOverflowError 。這兩種情況都是由應用程序控制之外的情況引起的,無法處理。
所以,最好不要在catch中使用Throwable,除非你完全確定自己處于一個特殊的情況下,并且你需要處理一個錯誤。
public void doNotCatchThrowable() {
try {
// do something
} catch (Throwable t) {
// don't do this!
}
}
這通常是由一個被忽略的異常引起的。開發(fā)人員可能非常確信它不會被拋出,并添加一個無法處理或無法記錄它的catch語句。當你發(fā)現它的時候,你很可能就會明白一句著名的話“This will never happen”。
public void doNotIgnoreExceptions() {
try {
// do something
} catch (NumberFormatException e) {
// this will never happen
}
}
是的,你可能在分析一個不可能發(fā)生的問題。
所以,請千萬不要忽略一個例外。你不會知道代碼在將來會發(fā)生什么變化。有些人可能會刪除阻止異常事件的驗證,而沒有意識到這造成了問題?;蛘邟伋霎惓5拇a被更改,現在拋出了同一個類的多個異常,而調用的代碼并不能阻止所有這些異常。
你至少應該寫一個日志信息,告訴每個人,需要檢查一下這個問題。
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;
}
當它發(fā)生時記錄一個異常,然后重新拋出它,以便調用者能夠適當地處理它,這可能會很直觀。但是它會為同一個異常寫多個錯誤消息。
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)
不添加任何額外的信息。正如在上述第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);
}
}
因此,只需要捕獲一個你想要處理的異常,在方法中指定它,并讓調用者處理它。
當你這樣做時,確保引用原始的異常處理。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);
}
}
總結
正如你所看到的,在拋出或捕獲異常時,有許多不同的事情需要考慮。以上大多數方法都可以提高代碼可讀性或API可用性。
異常通常是一個錯誤處理機制和一個通信媒介。因此,你應該確保同事一起討論想要應用的最佳實踐和方法,以便每個人都理解通用概念并以相同的方式使用它們