1.異常:這種情況下的異常电谣,可以通過(guò)完善任務(wù)重試機(jī)制秽梅,當(dāng)執(zhí)行異常時(shí)抹蚀,保存當(dāng)前任務(wù)信息加入重試隊(duì)列。重試的策略根據(jù)業(yè)務(wù)需要決定企垦,當(dāng)達(dá)到重試上限依然無(wú)法成功环壤,記錄任務(wù)執(zhí)行失敗,同時(shí)發(fā)出告警钞诡。
2.日志:類(lèi)比消息中間件郑现,處在不同線程之間的同一任務(wù),簡(jiǎn)單高效一點(diǎn)的做法可能是用traceId/requestId串聯(lián)荧降。有些日志系統(tǒng)本身支持MDC/NDC功能接箫,可以串聯(lián)相關(guān)聯(lián)的日志。
(需深入學(xué)習(xí)理解)
- Throwable:它是異常處理機(jī)制的基本組成類(lèi)型朵诫,在 Java 中只有 Throwable 類(lèi)型的實(shí)例才可以被拋出(throw)或者捕獲(catch)辛友。
- Exception 和 Error 都是繼承了 Throwable 類(lèi)。
Exception 和 Error 體現(xiàn)了 Java 平臺(tái)設(shè)設(shè)計(jì)者對(duì)不同異常情況的分類(lèi)剪返。
- Error:系統(tǒng)錯(cuò)誤废累,虛擬機(jī)出錯(cuò),我們處理不了脱盲,也不需要我們來(lái)處理邑滨。比如OutOfMeoryError。
- Exception:可以捕獲的異常钱反,且作出處理掖看。也就是要么捕獲異常并作出處理,要么繼續(xù)拋出異常诈铛。
Exception又分為檢查型異常和非檢查型異常
- 檢查型異常:在源代碼里必須顯式地進(jìn)行捕獲處理乙各,這是編譯期檢查的一部分墨礁。如FileNotFoundException客戶(hù)端需要知道是文件沒(méi)有找到的問(wèn)題幢竹,客戶(hù)端可以通過(guò)其它方法來(lái)解決這個(gè)問(wèn)題如更換其它路徑等。
- 非檢查型異常:就是所謂的運(yùn)行時(shí)異常恩静,類(lèi)似 NullPointerException焕毫,通常是可以編碼避免的邏輯錯(cuò)誤,具體根據(jù)需要來(lái)判斷是否需要捕獲驶乾,并不會(huì)在編譯期強(qiáng)制要求邑飒。
第一,理解 Throwable级乐、Exception疙咸、Error 的設(shè)計(jì)和分類(lèi)。
掌握那些應(yīng)用最為廣泛的子類(lèi)
比如 NoClassDefFoundError 和ClassNotFoundException 有什么區(qū)別风科,這也是個(gè)經(jīng)典的入門(mén)題目撒轮。
常見(jiàn)異常:
- ActivityNotFoundException:該異常是當(dāng)調(diào)用了 startActivity() 之后乞旦,找不到匹配的 Activity 時(shí)拋出該異常。
- BadTokenException:當(dāng)添加一個(gè)新的 window 時(shí)题山,如果 LayoutParams 不合法兰粉,就會(huì)拋出該異常。
- ClassCastException:父類(lèi)可以通過(guò)強(qiáng)制類(lèi)型轉(zhuǎn)換成具體某個(gè)子類(lèi)顶瞳,但如果強(qiáng)轉(zhuǎn)的兩個(gè)類(lèi)之間不存在繼承關(guān)系玖姑,那么就會(huì)拋出該異常。
- ConcurrentModificationException:同時(shí)修改異常
- IndexOutOfBoundsException:數(shù)組越界異常
- NullPointerException:空指針異常
- IOException:IO 異常慨菱,屬于檢查型異常焰络,必須通過(guò) try catch 代碼塊捕獲才能通過(guò)編譯階段。
- OutOfMemoryError:內(nèi)存溢出錯(cuò)誤符喝,這類(lèi)問(wèn)題屬于 Error舔琅。
- StackOverflowError:這類(lèi)錯(cuò)誤很?chē)?yán)重,表示程序陷入了死循環(huán)當(dāng)中洲劣。
- NoClassDefFoundError:通常出現(xiàn)的場(chǎng)景是:編譯階段沒(méi)問(wèn)題备蚓,但程序運(yùn)行期間卻出現(xiàn)該問(wèn)題。原因一般是由于打包時(shí)囱稽,jar 出現(xiàn)問(wèn)題郊尝,部分類(lèi)沒(méi)有打包進(jìn)去,導(dǎo)致的問(wèn)題战惊。
- ClassNotFoundException:這個(gè)異常流昏,同樣屬于相關(guān)類(lèi)找不到的問(wèn)題,但出現(xiàn)的場(chǎng)景通常是由于程序中使用了反射吞获,或者動(dòng)態(tài)加載之類(lèi)的方式况凉,使用了錯(cuò)誤的類(lèi)名,導(dǎo)致的問(wèn)題各拷。還有可能是由于混淆導(dǎo)致刁绒。
- NumberFormatException:數(shù)字轉(zhuǎn)換格式異常。
第二烤黍,理解 Java 語(yǔ)言中操作 Throwable 的元素和實(shí)踐知市。
try-catch-finally:捕獲異常
try (BufferedReader br = new BufferedReader(…);
BufferedWriter writer = new BufferedWriter(…)) {
// Try-with-resources
// do something
catch ( IOException | XEception e) {// Multiple catch
// Handle it
} finally{
// do something
}
throw 是語(yǔ)句拋出一個(gè)異常。
public class ThrowTest {
public static void main(String[] args) {
try{
throwChecked(3);
}catch(Exception e) {
System.out.println(e.getMessage());
}
throwRuntime(-3);
}
//該方法內(nèi)拋出一個(gè)Exception異常對(duì)象速蕊,必須捕獲或拋給調(diào)用者
public static void throwChecked(int a) throws Exception {
if(a < 0) {
throw new Exception("a的值應(yīng)大于0嫂丙,不符合要求")
}
}
//該方法內(nèi)拋出一個(gè)RuntimeException對(duì)象,可以不理會(huì)直接交給JVM處理
public static void throwRuntime(int a) {
if(a < 0) {
throw new RuntimeException("a的值應(yīng)大于0规哲,不符合要求")
}
}
}
- throw語(yǔ)句用在方法體內(nèi),表示拋出異常,由方法體內(nèi)的語(yǔ)句處理
- throw是具體向外拋異常的動(dòng)作跟啤,所以它是拋出一個(gè)異常實(shí)例。
- throw要么和try-catch-finally語(yǔ)句配套使用,要么與throws配套使用
- 如果拋出的是RuntimeException則既可以顯示使用try…catch捕獲也可以不理會(huì)它
throws 是方法可能拋出異常的聲明隅肥。
- 用在聲明方法時(shí)关顷,表示該方法可能要拋出異常
- 給調(diào)用者處理或者交給JVM。JVM對(duì)異常的處理方式是:打印異常的跟蹤棧信息并終止程序運(yùn)行武福。
- throws可以聲明多個(gè)異常议双,用逗號(hào)隔開(kāi);
- 調(diào)用者必須做出處理(捕獲或繼續(xù)拋出)捉片。否則編譯是不會(huì)通過(guò)的平痰。
注意:
方法覆蓋的時(shí)候,如果子類(lèi)覆蓋了父類(lèi)的方法伍纫,子類(lèi)的方法不能聲明拋出比父類(lèi)更多的異常類(lèi)型宗雇。如果聲明的比父類(lèi)更多的異常類(lèi)型,編譯器是通不過(guò)的莹规。
遵循“Throw early catch late”原則
簡(jiǎn)單來(lái)說(shuō)就是:在異常出現(xiàn)時(shí)就將其拋出赔蒲,抓取應(yīng)該在能夠獲取足夠信息的時(shí)候。簡(jiǎn)單來(lái)說(shuō)良漱,底層的方法應(yīng)該更多的拋出異常舞虱,異常應(yīng)該更多的在頂層代碼中抓取處理。
第三母市,異常處理的兩大基本原則
- 盡量不要捕獲類(lèi)似 Exception 這樣的通用異常矾兜,而是應(yīng)該捕獲特定異常。
- 不要生吞(swallow)異常患久。
為什么要盡量捕獲特定異常椅寺?
- 軟件工程是門(mén)協(xié)作的藝術(shù),所以我們有義務(wù)讓自己的代碼能夠直觀地體現(xiàn)出盡量多的信息蒋失。
- Exception無(wú)法提供具體的異常信息返帕,不利于處理;強(qiáng)制客戶(hù)端處理(抓雀萃臁)一些不需要關(guān)注的異常荆萤。
什么是生吞異常?
- 生吞異常嫉髓,往往是基于假設(shè)這段代碼可能不會(huì)發(fā)生观腊,或者感覺(jué)忽略異常是無(wú)所謂的,直接try catch掉算行,不做任何處理,不把異常拋出來(lái)苫耸,或者也沒(méi)有輸出到日志(Logger)之類(lèi)州邢。
- 存在的隱患是:程序可能在后續(xù)代碼以不可控的方式結(jié)束。開(kāi)發(fā)人員就很難找到問(wèn)題所在,什么原因?qū)е碌漠惓量淌!?/li>
第四骗村,自定義異常
除了保證提供足夠的信息,還有兩點(diǎn)需要考慮:
- 是否需要定義成 Checked Exception
- 在保證診斷信息足夠的同時(shí)呀枢,也要考慮避免包含敏感信息胚股,因?yàn)槟菢涌赡軐?dǎo)致潛在的安全問(wèn)題。
public class AuctionException extends Exception {
//無(wú)參構(gòu)造
public AuctionException() {}
//含參構(gòu)造
//通過(guò)調(diào)用父類(lèi)的構(gòu)造器將字符串msg傳給異常對(duì)象的massage屬性裙秋,
//massage屬性就是對(duì)異常的描述
public AuctionException(String msg) {
super(msg);
}
}
第五琅拌,性能分析
- try-catch 代碼段會(huì)產(chǎn)生額外的性能開(kāi)銷(xiāo)
- Java 每實(shí)例化一個(gè) Exception,都會(huì)對(duì)當(dāng)時(shí)的棧進(jìn)行快照摘刑,這是一個(gè)相對(duì)比較重的操作进宝。
或者換個(gè)角度說(shuō),它往往會(huì)影響 JVM 對(duì)代碼進(jìn)行優(yōu)化枷恕。
建議
不要過(guò)度使用異常:對(duì)于完全已知的錯(cuò)誤應(yīng)編寫(xiě)處理這種錯(cuò)誤代碼從而提高代碼的健壯性党晋,只有外部的、不能確定的和不可預(yù)知的運(yùn)行時(shí)錯(cuò)誤使是用異常徐块,并且異常機(jī)制的效率低于正常的流程控制未玻。
僅捕獲有必要的代碼段,盡量不要一個(gè)大的 try包住整段的代碼胡控;與此同時(shí)深胳,利用異常控制代碼流程铜犬,也不是一個(gè)好主意舞终,遠(yuǎn)比我們通常意義上的條件語(yǔ)句(if/else、switch)要低效癣猾。
當(dāng)我們的服務(wù)出現(xiàn)反應(yīng)變慢敛劝、吞吐量下降的時(shí)候,檢查發(fā)生最頻繁的 Exception 也是一種思路纷宇。
心得感悟(摘抄評(píng)論區(qū))
1 不要推諉或延遲處理異常夸盟,就地解決最好,并且需要實(shí)實(shí)在在的進(jìn)行處理像捶,而不是只捕捉上陕,不動(dòng)作。
2 一個(gè)函數(shù)盡管拋出了多個(gè)異常拓春,但是只有一個(gè)異呈筒荆可被傳播到調(diào)用端。最后被拋出的異常時(shí)唯一被調(diào)用端接收的異常硼莽,其他異常都會(huì)被吞沒(méi)掩蓋庶溶。如果調(diào)用端要知道造成失敗的最初原因,程序之中就絕不能掩蓋任何異常。
3 不要在finally代碼塊中處理返回值偏螺。
4 按照我們程序員的慣性認(rèn)知:當(dāng)遇到return語(yǔ)句的時(shí)候行疏,執(zhí)行函數(shù)會(huì)立刻返回。但是套像,在Java語(yǔ)言中酿联,如果存在finally就會(huì)有例外。除了return語(yǔ)句夺巩,try代碼塊中的break或continue語(yǔ)句也可能使控制權(quán)進(jìn)入finally代碼塊贞让。
5 請(qǐng)勿在try代碼塊中調(diào)用return、break或continue語(yǔ)句劲够。萬(wàn)一無(wú)法避免震桶,一定要確保finally的存在不會(huì)改變函數(shù)的返回值。
6 函數(shù)返回值有兩種類(lèi)型:值類(lèi)型與對(duì)象引用征绎。對(duì)于對(duì)象引用蹲姐,要特別小心,如果在finally代碼塊中對(duì)函數(shù)返回的對(duì)象成員屬性進(jìn)行了修改人柿,即使不在finally塊中顯式調(diào)用return語(yǔ)句柴墩,這個(gè)修改也會(huì)作用于返回值上。
7 勿將異常用于控制流凫岖。
8 如無(wú)必要江咳,勿用異常。