Java編程異常處理最佳實(shí)踐【推薦】
Java中的異常處理不是一個(gè)簡(jiǎn)單的話(huà)題。初學(xué)者很難理解,甚至有經(jīng)驗(yàn)的開(kāi)發(fā)人員也會(huì)花幾個(gè)小時(shí)來(lái)討論應(yīng)該如何拋出或處理這些異常。這就是為什么大多數(shù)開(kāi)發(fā)團(tuán)隊(duì)都有自己的異常處理的規(guī)則和方法。如果你是一個(gè)團(tuán)隊(duì)的新手,你可能會(huì)驚訝于這些方法與你之前使用過(guò)的那些方法有多么不同。常見(jiàn)的異常類(lèi)型:
NullPointerException -空指針引用異常
ClassCastException-類(lèi)型強(qiáng)制轉(zhuǎn)換異常
lllegalArgumentException-傳遞非法參數(shù)異常
ArithmeticException-算術(shù)運(yùn)算異常
ArrayStoreException-向數(shù)組中存放與聲明類(lèi)型不兼容對(duì)象異常
IndexOutOfBoundsException-下標(biāo)越界異常
NegativeArraySizeException-創(chuàng)建一個(gè)大小為負(fù)數(shù)的數(shù)組錯(cuò)誤異常
NumberFormatException-數(shù)字格式異常
SecurityException-安全異常
UnsupportedOperationException-不支持的操作異常
EOFException:文件已結(jié)束異常
FileNotFoundException:文件未找到異常
SQLException:操作數(shù)據(jù)庫(kù)異常
IOException:輸入輸出異常
NoSuchMethodException:方法未找到異常
然而,有幾種異常處理的最佳方法被大多數(shù)開(kāi)發(fā)團(tuán)隊(duì)所使用。下面為常見(jiàn)的幾種實(shí)用的異常處理方法!
1. 在Finally中清理資源或者使用Try-With-Resource語(yǔ)句
通常情況下,你在try中使用了一個(gè)資源,比如 InputStream ,之后需要關(guān)閉它。在這種情況下,一個(gè)常見(jiàn)的錯(cuò)誤是在try的末尾關(guān)閉了資源。
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);
}
}
問(wèn)題是,只要不拋出異常,這種方法就可以很好地運(yùn)行。try內(nèi)的所有語(yǔ)句都將被執(zhí)行,資源也會(huì)被關(guān)閉。
但是你在try里調(diào)用了一個(gè)或多個(gè)可能拋出異常的方法,或者自己拋出異常。這意味著可能無(wú)法到達(dá)try的末尾。因此,將不會(huì)關(guān)閉這些資源。
所以應(yīng)該將清理資源的代碼放入Finally中,或者使用Try-With-Resource語(yǔ)句。
使用Finally
相比于try,無(wú)論是在成功執(zhí)行try里的代碼后,或是在catch中處理了一個(gè)異常后,F(xiàn)inally里的內(nèi)容是一定會(huì)被執(zhí)行的。因此,可以確保清理所有已打開(kāi)的資源。
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語(yǔ)句
另一個(gè)選擇是Try-With-Resource語(yǔ)句,在 introduction to Java exception handling 中更詳細(xì)地說(shuō)明了這一點(diǎn)。
如果你的資源實(shí)現(xiàn)了 AutoCloseable 接口,就可以使用它,這正是大多數(shù)Java標(biāo)準(zhǔn)資源所做的。當(dāng)你在try子句中打開(kāi)資源時(shí),它將在try被執(zhí)行后自動(dòng)關(guān)閉,或者處理一個(gè)異常。
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);
}
}
2. 給出準(zhǔn)確的異常處理信息
你拋出的異常越具體越好。一定要記住,一個(gè)不太了解你代碼的同事,也許幾個(gè)月后,需要調(diào)用你的方法,并且處理這個(gè)異常。
因此,請(qǐng)確保提供盡可能多的信息,這會(huì)使你的API更容易理解。因此,你方法的調(diào)用者將能夠更好地處理異常,或者通過(guò)額外的檢查來(lái)避免它。
所以,要盡量能更好地描述你的異常處理信息,比如用 NumberFormatException 代替 IllegalArgumentException ,避免拋出一個(gè)不具體的異常。
public void doNotDoThis() throws Exception {
...
}
public void doThis() throws NumberFormatException {
...
}
3. 記錄你所指定的異常
當(dāng)你在方法中指定一個(gè)異常時(shí),你應(yīng)該在Javadoc中記錄下它。這與前面提到的方法有著相同的目標(biāo):為調(diào)用者提供盡可能多的信息,這樣他們就可以避免異常或者更容易地處理異常。
因此,請(qǐng)確保在Javadoc中添加一個(gè)@throws 聲明,并描述可能導(dǎo)致的異常情況。
/**
* This method does something extremely useful ...
*
* @param input
* @throws MyBusinessException if ... happens
*/
public void doSomething(String input) throws MyBusinessException {
...
}
4. 使用描述性消息拋出異常
這一最佳實(shí)踐的理念與前兩個(gè)相似。但這一次,你不用給調(diào)用方法的人提供信息。異常消息會(huì)被所有人讀取,同時(shí)必須了解在日志文件或監(jiān)視工具中報(bào)告異常時(shí)發(fā)生了什么。
因此,應(yīng)該盡可能準(zhǔn)確地描述問(wèn)題,并提供相關(guān)的信息來(lái)了解異常事件。
別誤會(huì),你不需要寫(xiě)一段文字,而是應(yīng)該用1-2個(gè)簡(jiǎn)短的句子解釋異常的原因。這可以幫助開(kāi)發(fā)團(tuán)隊(duì)理解問(wèn)題的嚴(yán)重性,同時(shí)也使你能夠更容易地分析任何服務(wù)事件。
如果拋出一個(gè)特定的異常,它的類(lèi)名很可能已經(jīng)描述了這種類(lèi)型的錯(cuò)誤。所以,你不需要提供很多額外的信息。一個(gè)很好的例子就是,當(dāng)你以錯(cuò)誤的格式使用字符串時(shí),如NumberFormatException,它就會(huì)被類(lèi) java.lang.Long的構(gòu)造函數(shù)拋出。
try {
new Long("xyz");
} catch (NumberFormatException e) {
log.error(e);
}
NumberFormatException已經(jīng)告訴你問(wèn)題的類(lèi)型,所以只需要提供導(dǎo)致問(wèn)題的輸入字符串。如果異常類(lèi)的名稱(chēng)不具有表達(dá)性,那么就需要提供必要的解釋信息。
17:17:26,386 ERROR TestExceptionHandling:52 - java.lang.NumberFormatException: For input string: "xyz"
5. 最先捕獲特定的異常
大多數(shù)IDE都可以幫助你做到這點(diǎn),當(dāng)你試圖捕獲不確定的異常時(shí),它會(huì)報(bào)告一個(gè)不可到達(dá)的代碼塊。
問(wèn)題是只有第一個(gè)匹配到異常的catch語(yǔ)句才會(huì)被執(zhí)行,所以,如果你最先發(fā)現(xiàn)IllegalArgumentException,你將永遠(yuǎn)不會(huì)到達(dá)catch里處理更具體的NumberFormatException,因?yàn)樗荌llegalArgumentException的一個(gè)子類(lèi)。
所以要首先捕獲特定的異常類(lèi),并在末尾添加一些處理不是很具體異常的catch語(yǔ)句。
你可以在下面的代碼片段中看到這樣一個(gè)try-catch語(yǔ)句的示例。第一個(gè)catch處理所有NumberFormatExceptions異常,第二個(gè)catch 處理NumberFormatException異常以外的illegalargumentexception異常。
public void catchMostSpecificExceptionFirst() {
try {
doSomething("A message");
} catch (NumberFormatException e) {
log.error(e);
} catch (IllegalArgumentException e) {
log.error(e)
}
}
6. 不要在catch中使用Throwable
Throwable 是exceptions 和 errors的父類(lèi)。當(dāng)然,你可以在catch子句中使用它,但其實(shí)你不應(yīng)該這樣做。
如果你在catch子句中使用Throwable,它將不僅捕獲所有的異常,還會(huì)捕獲所有錯(cuò)誤。JVM會(huì)拋出錯(cuò)誤,這是應(yīng)用程序不打算處理的嚴(yán)重問(wèn)題。典型的例子是 OutOfMemoryError 或 StackOverflowError 。這兩種情況都是由應(yīng)用程序控制之外的情況引起的,無(wú)法處理。
所以,最好不要在catch中使用Throwable,除非你完全確定自己處于一個(gè)特殊的情況下,并且你需要處理一個(gè)錯(cuò)誤。
public void doNotCatchThrowable() {
try {
// do something
} catch (Throwable t) {
// don't do this!
}
}
7. 不要忽略Exceptions
你是否曾經(jīng)分析過(guò)只有用例的第一部分才被執(zhí)行的bug報(bào)告嗎?
這通常是由一個(gè)被忽略的異常引起的。開(kāi)發(fā)人員可能非常確信它不會(huì)被拋出,并添加一個(gè)無(wú)法處理或無(wú)法記錄它的catch語(yǔ)句。當(dāng)你發(fā)現(xiàn)它的時(shí)候,你很可能就會(huì)明白一句著名的話(huà)“This will never happen”。
public void doNotIgnoreExceptions() {
try {
// do something
} catch (NumberFormatException e) {
// this will never happen
}
}
是的,你可能在分析一個(gè)不可能發(fā)生的問(wèn)題。
所以,請(qǐng)千萬(wàn)不要忽略一個(gè)例外。你不會(huì)知道代碼在將來(lái)會(huì)發(fā)生什么變化。有些人可能會(huì)刪除阻止異常事件的驗(yàn)證,而沒(méi)有意識(shí)到這造成了問(wèn)題?;蛘邟伋霎惓5拇a被更改,現(xiàn)在拋出了同一個(gè)類(lèi)的多個(gè)異常,而調(diào)用的代碼并不能阻止所有這些異常。
你至少應(yīng)該寫(xiě)一個(gè)日志信息,告訴每個(gè)人,需要檢查一下這個(gè)問(wèn)題。
public void logAnException() {
try {
// do something
} catch (NumberFormatException e) {
log.error("This should never happen: " + e);
}
}
8. 不要記錄和拋出一個(gè)異常
這可能是最常被忽略的。你可以在許多代碼片段或者庫(kù)文件里發(fā)現(xiàn),有異常會(huì)被捕獲、記錄和重新拋出。
try {
new Long("xyz");
} catch (NumberFormatException e) {
log.error(e);
throw e;
}
當(dāng)它發(fā)生時(shí)記錄一個(gè)異常,然后重新拋出它,以便調(diào)用者能夠適當(dāng)?shù)靥幚硭?,這可能會(huì)很直觀(guān)。但是它會(huì)為同一個(gè)異常寫(xiě)多個(gè)錯(cuò)誤消息。
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個(gè)中所解釋的那樣,異常消息應(yīng)該描述異常事件。堆棧會(huì)告訴你在哪個(gè)類(lèi)、方法和行中異常被拋出。
如果你需要添加額外的信息,應(yīng)該捕獲異常并將其包裝在一個(gè)自定義的信息中。但要確保遵循下面的第9條。
public void wrapException(String input) throws MyBusinessException {
try {
// do something
} catch (NumberFormatException e) {
throw new MyBusinessException("A message that describes the error.", e);
}
}
因此,只需要捕獲一個(gè)你想要處理的異常,在方法中指定它,并讓調(diào)用者處理它。
9. 包裝異常
有時(shí)最好捕獲一個(gè)標(biāo)準(zhǔn)異常并將其封裝到一個(gè)定制的異常中。此類(lèi)異常的典型例子是應(yīng)用程序或框架特定的業(yè)務(wù)異常。這允許你添加額外的信息,并且也可以為異常類(lèi)實(shí)現(xiàn)一個(gè)特殊的處理。
當(dāng)你這樣做時(shí),確保引用原始的異常處理。Exception類(lèi)提供了一些特定的構(gòu)造函數(shù)方法,這些方法可以接受Throwable作為參數(shù)。否則,你將丟失原始異常的堆棧跟蹤和消息,這將使你很難分析導(dǎo)致異常的事件。
public void wrapException(String input) throws MyBusinessException {
try {
// do something
} catch (NumberFormatException e) {
throw new MyBusinessException("A message that describes the error.", e);
}
}
總結(jié)
正如你所看到的,在拋出或捕獲異常時(shí),有許多不同的事情需要考慮。以上大多數(shù)方法都可以提高代碼可讀性或API可用性。異常通常是一個(gè)錯(cuò)誤處理機(jī)制和一個(gè)通信媒介。因此,你應(yīng)該確保同事一起討論想要應(yīng)用的最佳實(shí)踐和方法,以便每個(gè)人都理解通用概念并以相同的方式使用它們。
以上就是本文關(guān)于Java編程異常處理最佳實(shí)踐的全部?jī)?nèi)容,希望對(duì)大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站:Java編程中的檢查型異常與非檢查型異常分析、Java多線(xiàn)程之線(xiàn)程通信生產(chǎn)者消費(fèi)者模式及等待喚醒機(jī)制代碼詳解等,如有不足之處,歡迎留言指出。感謝朋友們對(duì)本站的支持!
相關(guān)文章
Springboot整合WebSocket實(shí)戰(zhàn)教程
WebSocket使得客戶(hù)端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡(jiǎn)單,允許服務(wù)端主動(dòng)向客戶(hù)端推送數(shù)據(jù),這篇文章主要介紹了Springboot整合WebSocket實(shí)戰(zhàn)教程,需要的朋友可以參考下2023-05-05
java編程SpringSecurity入門(mén)原理及應(yīng)用簡(jiǎn)介
Spring 是非常流行和成功的 Java 應(yīng)用開(kāi)發(fā)框架,Spring Security 正是 Spring 家族中的成員。Spring Security 基于 Spring 框架,提供了一套 Web 應(yīng)用安全性的完整解決方案2021-09-09
深入學(xué)習(xí)java并發(fā)包ConcurrentHashMap源碼
這篇文章主要介紹了深入學(xué)習(xí)java并發(fā)包ConcurrentHashMap源碼,整個(gè) ConcurrentHashMap 由一個(gè)個(gè) Segment 組成,Segment 代表”部分“或”一段“的意思,所以很多地方都會(huì)將其描述為分段鎖。,需要的朋友可以參考下2019-06-06
SpringBoot訪(fǎng)問(wèn)請(qǐng)求404解決方法
這篇文章主要介紹了SpringBoot訪(fǎng)問(wèn)請(qǐng)求404解決方法,文中有詳細(xì)的解決方法供大家參考,對(duì)我們學(xué)習(xí)或工作有一定的幫助,需要的朋友跟著小編一起來(lái)學(xué)習(xí)吧2023-07-07
SpringBoot應(yīng)用監(jiān)控Actuator使用隱患及解決方案
SpringBoot的Actuator 模塊提供了生產(chǎn)級(jí)別的功能,比如健康檢查,審計(jì),指標(biāo)收集,HTTP 跟蹤等,幫助我們監(jiān)控和管理Spring Boot 應(yīng)用,本文將給大家介紹SpringBoot應(yīng)用監(jiān)控Actuator使用隱患及解決方案,需要的朋友可以參考下2024-07-07
如何實(shí)現(xiàn)自己的spring boot starter
這篇文章主要介紹了如何實(shí)現(xiàn)自己的spring boot starter,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08
Spring中的@Scheduled定時(shí)任務(wù)注解詳解
這篇文章主要介紹了Spring中的@Scheduled定時(shí)任務(wù)注解詳解,要使用@Scheduled注解,首先需要在啟動(dòng)類(lèi)添加@EnableScheduling,啟用Spring的計(jì)劃任務(wù)執(zhí)行功能,這樣可以在容器中的任何Spring管理的bean上檢測(cè)@Scheduled注解,執(zhí)行計(jì)劃任務(wù),需要的朋友可以參考下2023-09-09
Java中final,finally,finalize三個(gè)關(guān)鍵字的區(qū)別_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章給大家收集整理了有關(guān)java中final,finally,finalize三個(gè)關(guān)鍵字的區(qū)別介紹,非常不錯(cuò),具有參考借鑒價(jià)值,需要的的朋友參考下吧2017-04-04

