1. 程式人生 > >Exception 和 Error 有什麽區別麽

Exception 和 Error 有什麽區別麽

have 提高 參考 隱式 any studio denied auto wro

聲明

本篇所涉及的提問,正文的知識點,全都來自於楊曉峰的《Java核心技術36講》,當然,我並不會全文照搬過來,畢竟這是付費的課程,應該會涉及到侵權之類的問題。

所以,本篇正文中的知識點,是我從課程中將知識點消耗後,用個人的理解、觀念所表達出來的文字,參考了原文,但由於是個人理解,因此不保證觀點完全正確,也不代表錯誤的觀點是課程所表達的。如果這樣仍舊還是侵權了,請告知,會將發表的文章刪掉。

當然,如果你對此課程有興趣,建議你自己也購買一下,新用戶立減 30,微信掃碼訂閱時還可以返現 6 元,相當於 32 元購買 36 講的文章,每篇文章還不到 1 元,蠻劃算的了。

技術分享圖片

提問

  • Exception 和 Error 有什麽區別?
  • 運行時異常和一般異常有什麽區別?
  • 你了解哪些常見的 Error,Exception,RuntimeException?
  • NoClassDefFoundError 和 ClassNotFoundException 有什麽區別?
  • 異常處理的代碼有哪些比較良好的規範?

正文

對於這個問題,感覺我講不了很多,頂多都是一些概念性回答而已。

Exception

Exception 是程序正常運行時,可以預料到的意外情況,可以被捕獲,也應該進行相應異常處理。

Exception 繼承自 Throwable,具體又可劃分為 RuntimeException 運行時異常和一般異常。兩者的區別在於運行時異常在編譯階段可以不用進行捕獲,這類異常通常都是在 Lint 檢查過程中,或者程序運行期間才暴露出來的異常,因此也可以被歸類為非檢查型異常。

一般異常則是在編譯期間就必須進行異常捕獲,因此也被歸類為檢查型異常。

Error

Error 也是繼承自 Throwable,同樣會造成程序崩潰退出,但跟異常不大一樣的是,這類錯誤問題,通常是由於 JVM 運行狀態出了問題導致,我們不應捕獲處理。要做的,應該是分析該錯誤出現的原因,盡量避免這類問題的出現。

關於 Exception 和 Error 的區別,可以簡單這麽理解,我們可以從異常中恢復程序但卻不應該嘗試從錯誤中恢復程序。

以上,基本就是我對於該講問題所能想到的最大限度的點了。看了該講作者所擴展的點,以及評論區裏大神的回復,其實還可以從常見的一些異常,即原因和處理方式擴展;也可以從異常處理代碼的規範角度出發擴展講一講,我都統一將這些擴展都在開頭的提問中列出來了。

常見的 Exception 或 Error

想查閱相關的 Exception 或 Error,如果你記得該異常的名稱,那可以直接通過 Android Stduio 查閱相關源碼即可。

如果想翻看所有的類別,那麽也可通過 AS 的 Hierarchy 功能查閱,快捷鍵 Ctrl + H,如下:

技術分享圖片

在這裏翻看、過濾你熟悉的,或者想找的異常或錯誤,點進去查看相關源碼說明即可。

至於常見的 Exception,RuntiomeException,Error,我針對個人在項目中較常遇見,目前印象較深的畫了張類圖:

技術分享圖片

  • ActivityNotFoundException

源碼註釋裏說了,該異常是當調用了 startActivity() 之後,找不到匹配的 Activity 時拋出該異常。也就是說,通常通過隱式 Intent 打開 Activity,或者通過廣播,URI 等方式,不註意一點的話,可能會出現該異常。

如果有使用到這些場景,可以考慮是否增加異常捕獲,防止使用不當造成異常。

  • BadTokenException

這裏的異常指的是 WindowManager 內部類 BadTokenException,顯然,當添加一個新的 window 時,如果 LayoutParams 不合法,就會拋出該異常。

添加 window 的場景,除了手動通過 WindowManager 的 addView() 的場景外,其實打開一個新的 Activity,新的 Dialog,內部也是通過 WindowManager 來 addView() 的,因此,這些場景下都是有可能發生該異常的。

不過,這個異常的日誌會比較詳細,因為在 ViewRootImpl 的 setView() 中,會去細分參數不合法的類別,附上部分源碼:

//ViewRootImpl#setView()
 
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    switch (res) {
            case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
            case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
                throw new WindowManager.BadTokenException(
                        "Unable to add window -- token " + attrs.token
                                + " is not valid; is your activity running?");
            case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
                throw new WindowManager.BadTokenException(
                        "Unable to add window -- token " + attrs.token
                                + " is not for an application");
            case WindowManagerGlobal.ADD_APP_EXITING:
                throw new WindowManager.BadTokenException(
                        "Unable to add window -- app for token " + attrs.token
                                + " is exiting");
            case WindowManagerGlobal.ADD_DUPLICATE_ADD:
                throw new WindowManager.BadTokenException(
                        "Unable to add window -- window " + mWindow
                                + " has already been added");
            case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
                // Silently ignore -- we would have just removed it
                // right away, anyway.
                return;
            case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
                throw new WindowManager.BadTokenException("Unable to add window "
                        + mWindow + " -- another window of type "
                        + mWindowAttributes.type + " already exists");
            case WindowManagerGlobal.ADD_PERMISSION_DENIED:
                throw new WindowManager.BadTokenException("Unable to add window "
                        + mWindow + " -- permission denied for window type "
                        + mWindowAttributes.type);
}
  • ClassCastException

父類可以通過強制類型轉換成具體某個子類,但如果強轉的兩個類之間不存在繼承關系,那麽就會拋出該異常。

如果不確定需要強轉的兩個類的關系,可以先通過關鍵字 instanceof 進行判斷。

  • ConcurrentModificationException

這異常則是由於一些不恰當的集合操作導致,比如遍歷集合的過程中,進行了不恰當的刪除操作;或者有某個線程正在遍歷集合,另外一個線程則對該集合進行的修改操作;

相對應的避免方法網上也很多,比如遍歷集合刪除的操作通過叠代器來實現等等。

  • IndexOutOfBoundsException

數組越界異常,這類異常還蠻經常出現的,避免方式就只能是盡量書寫規範的代碼,註意一些,或者多讓程序跑跑 Lint 檢查。

  • NullPointerException

空指針異常,這異常算是最令人頭疼的異常了,在線上異常的比例中,肯定不少。

而且出現情況有時還很難分析,代碼流程上查看,明明不會出現空指針場景,但現實就是有用戶的的確確出現了。

解決時,如果可以,盡量不要簡單的加個非空判斷,在程序中各個地方加非空判斷,其實是種特別不優雅的行為。如果能明確為什麽會出現為空的場景,如何解決,這是最好的,而不是每次都簡單的加個非空判斷。

場景很多很多,之前也有寫過一篇專門處理實體類的空判斷文章,感興趣的可以看看:

分享兩個提高效率的AndroidStudio小技巧

  • IOException

IO 異常,屬於檢查型異常,必須通過 try catch 代碼塊捕獲才能通過編譯階段,這也就沒什麽好介紹的了。

  • OutOfMemoryError

內存溢出錯誤,這類問題屬於 Error,不屬於 Exception,所以不要期待解決這類問題僅僅通過捕獲就可以處理。

針對 Error 這類問題,我們沒法捕獲處理,只能是從避免的角度出發,分析出現的原因,盡量不用出現這類問題。

造成內存溢出的問題,有多種,大概就是圖片問題、內存泄漏問題。

針對圖片使用的優化處理,網上很多,各種壓縮、降分辨率等等方式。

針對內存泄漏,一是開發期間遵守規範的代碼行為,盡量避免寫出有內存泄漏的隱患;二是發生內存泄漏後,借助相應工具進行定位分析。

  • StackOverflowError

這類錯誤很嚴重,表示程序陷入了死循環當中,原因也就是你寫了有問題的代碼。

因此,當出現這類問題,最好盡快定位處理。

  • NoClassDefFoundError

這類問題,通常出現的場景是:編譯階段沒問題,但程序運行期間卻出現該問題。

原因一般是由於打包時,jar 出現問題,部分類沒有打包進去,導致的問題。

  • ClassNotFoundException

這個異常,同樣屬於相關類找不到的問題,但出現的場景通常是由於程序中使用了反射,或者動態加載之類的方式,使用了錯誤的類名,導致的問題。

還有可能是由於混淆導致。

異常處理良好規範

  • 盡量不要捕獲類似 Exception 這樣通用的異常,而是應該捕獲特定異常

這是因為在日常的開發和合作中,我們讀代碼的機會往往超過寫代碼,軟件工程是門協作藝術,所以我們有義務讓自己的代碼能夠直觀的體現出盡量多的信息,而泛泛的 Exception 之類,恰恰隱藏了我們的目的。另外,我們也要保證程序不會捕獲到我們不希望捕獲的異常。比如,你可能更希望 RuntimeException 被擴散出來,而不是被捕獲。

進一步講,除非深思熟慮了,否則不要捕獲 Throwable 或者 Error,這樣很難保證我們能夠正確處理異常。

  • 不要生吞異常

如果我們不把異常拋出來,或者也沒有輸出到日誌之類,程序可能在後續代碼以不可控的方式結束。沒人能夠輕易判斷究竟是哪裏拋出了異常,以及是什麽原因產生了異常。

  • try-catch 代碼段會產生額外的性能開銷

try-catch 代碼段往往會影響 JVM 對代碼進行優化,所以建議僅捕獲有必要的代碼段,盡量不要一個大的 try 包住整段的代碼;與此同時,利用異常控制代碼流程,也不是一個好主意,遠比我們通常意義上的條件語句 (if / else, switch)要低效

Java 每實例化一個 Exception,都會對當時的棧進行快照,這是一個相對比較重的操作,如果發生的非常頻繁,這個開銷可就不能被忽略了。

  • 不要在 finally 代碼塊中處理返回值

按照我們程序員的慣性認知:當遇到 return 語句的時候,執行函數會立刻返回。但是,在 Java 語言中,如果存在 finally 就會有例外。除了 return 語句, try 代碼塊中的 break 或 continue 語句也可能使控制權進入 finally 代碼塊。

請勿在 try 代碼塊中調用 return, break, continue 語句。萬一無法避免,一定要確保 finally 的存在不會改變函數的返回值。

函數的返回值有兩種類型:值類型和對象引用,對於對象引用,要特別小心,如果在 finally 代碼塊中對函數返回的對象成員屬性進行了修改,即使不在 finally 塊中顯示調用 return 語句,這個修改也會作用於返回值上。

  • 當一個 try 後跟了很多個 catch 時,必須先捕獲小的異常再捕獲大的異常。
    ***
    大家好,我是 dasu,歡迎關註我的公眾號(dasuAndroidTv),如果你覺得本篇內容有幫助到你,可以轉載但記得要關註,要標明原文哦,謝謝支持~
    技術分享圖片

Exception 和 Error 有什麽區別麽