Java異常梳理

定義

java中的異常提供了一種識別及響應(yīng)錯誤情況的一致性機制,若有效地處理異常能使程序更加的健壯,且更易于調(diào)試仲吏。異常之所以是一種強大的調(diào)試手段,在于其回答了三個問題:什么出了錯暂题?在哪出的錯?為什么出錯究珊?
異常的類型回答了“什么”被拋出(什么出了錯)薪者,異常的堆棧信息回答了“在哪”拋出(在哪出的錯),異常的函數(shù)調(diào)用信息回答了“為什么”會拋出(為什么出錯)剿涮,如果你的異常沒有回答以上全部問題言津,那么可能你沒有很好地使用它們。

異常分類

異常整體上可以分為以下3種類型:

  • 運行時異常:是RuntimeException類及其子類標識的異常取试,程序中可以選擇捕獲處理悬槽,也可以不處理。這些異常一般是由程序邏輯錯誤引起的瞬浓,我們需要從程序邏輯的角度盡可能避免這類異常的發(fā)生初婆。(運行時異常的特點是Java編譯器不會檢查它)
  • 檢查性異常:正確的程序在運行中,很容易出現(xiàn)的猿棉、情理可容的異常狀況磅叛。檢查性異常雖然是異常狀況,但在一定程度上它的發(fā)生是可以預(yù)計的萨赁,而且一旦發(fā)生這種異常狀況弊琴,需要采取某種方式進行處理(捕獲處理或者繼續(xù)拋出)。(這些異常在編譯時不能被簡單地忽略杖爽,必須捕獲處理或繼續(xù)拋出)
  • 錯誤Error類型及子類是程序所無法處理的錯誤访雪,表示運行的應(yīng)用程序中的較嚴重問題。大多數(shù)錯誤與代碼編寫者執(zhí)行的操作無關(guān)掂林,而是表示代碼運行時JVM(Java 虛擬機)出現(xiàn)的問題臣缀。如:當JVM不再有繼續(xù)執(zhí)行操作所需的內(nèi)存資源時將出現(xiàn)OutOfMemoryError等。

具體使用

略泻帮。(本篇文章不打算介紹基本使用)

異常處理三原則

使用以下三個原則可以幫助我們在調(diào)試過程中最大限度地使用好異常機制精置。

  1. 具體明確
    當我們需要拋出一個異常時,應(yīng)盡可能的使用能描述具體問題的異常子類锣杂,而不是new一個通用的基類(如錯誤寫法:throw new Exception("test exception"); )脂倦。其目的是為了在捕獲異常的時候,我們能根據(jù)異常的類型一眼就能分辨什么出了錯元莫,另外更重要的是捕獲異常處理時赖阻,能將異常類型對應(yīng)到不同的catch塊,以便針對不同的異常做出不同的處理踱蠢。
    比如火欧,異常IOException更加特化的異常FileNotFoundException棋电、EOFExceptionObjectStreamException,這些都是IOException的子類苇侵,每一種都描述了一類特定的I/O錯誤:分別是文件不存在赶盔,異常文件結(jié)尾和錯誤的序列化對象流。

  2. 提早拋出
    提早拋出異常的意思是榆浓,在可預(yù)見的異常前通過程序代碼檢查可能的異常(特別是運行時異常)于未,要么通過if條件過濾即將拋出的異常使整個函數(shù)調(diào)用立即返回,要么構(gòu)造一個更加特例的異常提前拋出陡鹃。其目的是為了能更清晰的定義一些可能預(yù)知的異常烘浦、避免不必要的對象構(gòu)造或資源占用,比如文件或網(wǎng)絡(luò)連接萍鲸,還能避開了資源操作所帶來的清理動作谎倔。
    比如:

testException.readValueFromFile(null); //測試傳入一個為null的文件名

public int readValueFromFile(String filename) {
    int size = 0;
    InputStream in = null;
    try {
        in = new FileInputStream(filename); //FileInputStream會拋出異常
    } catch (Exception e) {
        
    } finally {
        in.close();
    }
    return 0;
}

將會拋出一下異常:

java.lang.NullPointerException: Attempt to invoke virtual method 'char[] java.lang.String.toCharArray()' on a null object reference
at java.io.File.fixSlashes(File.java:183)
at java.io.File.<init>(File.java:130)
at java.io.FileInputStream.<init>(FileInputStream.java:103)
at com.android.test.demo.exception.TestException.readValueFromFile(TestException.java:22)
at com.android.test.demo.MainActivity.testException(MainActivity.java:56)
at com.android.test.demo.MainActivity.onCreate(MainActivity.java:41)

從異常堆棧信息看:是FileInputStream的構(gòu)造函數(shù)中拋出了NullPointerException異常,而JDK API一般不會出錯的猿推,很可能是我們的調(diào)用邏輯有問題片习,但異常堆棧卻不能清晰的看出到底是什么為空導(dǎo)致的空指針,假設(shè)我們回退堆棧和檢查程序最終還是會發(fā)現(xiàn)是filename參數(shù)傳了空導(dǎo)致的蹬叭。
??如果我們在實例化FileInputStream之前做一次參數(shù)檢查藕咏,若為空時提早拋出IllegalArgumentException,如下所示:

    if (filename == null){
         throw new IllegalArgumentException("filename is null");
    }

則會拋出這樣的堆棧信息:

java.lang.IllegalArgumentException: filename is null
at com.android.test.demo.exception.TestException.readValueFromFile(TestException.java:23)
at com.android.test.demo.MainActivity.testException(MainActivity.java:56)
at com.android.test.demo.MainActivity.onCreate(MainActivity.java:41)

此時堆棧信息不會深入到FileInputStream中去了(一般情況不會是JDK的API出現(xiàn)異常)秽五,而是明確的告訴了我們?nèi)齻€問題:出了什么錯(提供了非法參數(shù)值)坦喘,為什么出錯(文件名不能為空值)盲再,以及哪里出的錯(readValueFromFile()函數(shù))瓣铣。

  1. 延遲捕獲
    延遲捕獲的意思是答朋,當函數(shù)出現(xiàn)異常且在當前函數(shù)無法做出有效處理時,應(yīng)當將其拋給函數(shù)的調(diào)用者去處理棠笑,以便調(diào)用者有機會通過不同的參數(shù)從異常中恢復(fù)出來。比如础钠,在調(diào)用者第一次傳入錯誤的參數(shù)導(dǎo)致異常后翻具,調(diào)用者仍有機會(在catch{}中)再一次傳入某個默認參數(shù)來使該函數(shù)調(diào)用成功工禾。
    ??大多數(shù)人可能都會犯的一個錯是槽畔,在程序有能力處理異常之前就捕獲了它。Java編譯器要求檢查出的異常必須被捕獲或拋出間接助長了這種行為拾给,大家自然而然的做法就是立即將代碼用try塊包裝起來额衙,并使用catch捕獲異常,以免編譯器報錯。如果當前函數(shù)有能力處理異常還好,不然只能僅僅打印一下異常堆棧信息耸黑,無法通過異常機制對函數(shù)的執(zhí)行提供有效的幫助大刊。

finally{}語句塊的執(zhí)行情況

一般的描述是:try{}里有一個return語句,那么緊跟在這個try后的finally{}里的code會不會被執(zhí)行三椿,什么時候被執(zhí)行缺菌,在return前還是后。會不會影響返回值搜锰?
先給結(jié)論:finally塊的語句在trycatch中的return語句執(zhí)行之后返回之前執(zhí)行伴郁,且finally里的修改語句可能影響也可能不影響trycatchreturn已經(jīng)確定的返回值(由返回值的傳遞類型決定:傳值還是傳地址),若finally里也有return語句則覆蓋trycatch中的return語句直接返回蛋叼。
測試1:

//測試調(diào)用
final int value = testException.testFinally1();
Log.d(TAG, "testFinally1 return size: " + value);

public int testFinally1() {
    int x = 1;
    try {
        ++x;
        Log.d(TAG, "try{} x: " + x);
        return returnSize(x);
    } finally { //finally塊在retrun語句執(zhí)行執(zhí)行之后焊傅,返回之前執(zhí)行
        ++x;
        Log.d(TAG, "finally{} x: " + x);
    }
}

private int returnSize(int size) {
    Log.d(TAG, "enter returnSize()");
    return size;
}

執(zhí)行結(jié)果:

TestException( 1468): try{} x: 2
TestException( 1468): enter returnSize()
TestException( 1468): finally{} x: 3
TestException( 1468): testFinally1 return size: 2

可以看到函數(shù)returnSizefinally{}塊之前執(zhí)行了(finally{}return語句執(zhí)行之后,返回之前執(zhí)行)狈涮,且finally{}++x沒有生效(return的時候是復(fù)制了一份變量然后返回狐胎,所以之后finally操作的變量如果是基本類型的話不會影響返回值)

測試2:

//測試調(diào)用
Map<String, String> map = testException.testFinally2();
Log.d(TAG, "testFinally2 map: " + (map != null ? map.get("key") : "null"));

public Map<String, String> testFinally2() {
    Map<String, String> result = new HashMap<>();
    result.put("key", "start");
    try {
        result.put("key", "try");
        return result;
    } catch (Exception e) {
        result.put("key", "catch");
    } finally {
        result.put("key", "finally"); //此處生效
        result = null; //此處不生效
    }
    return result;
}

執(zhí)行結(jié)果:

TestException( 1468): testFinally2 map: finally

從測試結(jié)果我們可以看到,finally{}的code生效了歌馍,這是因為返回值為引用類型握巢,雖然在return的時候是復(fù)制了一份變量然后返回,但該變量指向的是同一個對象松却,因此這里的操作會反映到返回結(jié)果中暴浦;result = null;這句代碼不生效的原因同測試1溅话。

另外,finally里也有return語句時歌焦,則覆蓋trycatch中的return語句直接返回飞几。----原因也可以從上面兩個測試例子中得出結(jié)論。

自定義異常

在開發(fā)過程中独撇,自我認知范圍內(nèi)需要使用自定義異常的情況總結(jié)為以下兩種:

  1. 自定義異常繼承自某個相關(guān)異常屑墨,拋出自定義異常時,信息可以根據(jù)情況自定義券勺,從而使得異承髟浚可以隱藏底層的異常中灿里,使得信息更安全关炼、也更加直觀。因為自定義異诚坏酰可以拋出我們自己想要拋出的信息儒拂,也可以通過拋出的信息區(qū)分異常發(fā)生的位置、根據(jù)異常名我們就可以知道哪里有異常色鸳,根據(jù)異常提示信息進行程序修改社痛。比如空指針異常NullPointException,我們可以拋出信息為“xxx為空”定位異常位置命雀,而不用輸出堆棧信息蒜哀。
    測試:
//自定義異常類
public class TestGHException extends RuntimeException {

    public TestGHException(Throwable cause) {
        super(cause);
    }

    public TestGHException(String message, Throwable cause, boolean enableSuppression,
        boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }


    public TestGHException(String message) {
        super(message);
    }

    public TestGHException(String message, Throwable cause) {
        super(message, cause);
    }
}

測試1(不使用自定義異常):

public void testGHException() {
    String value = "test";
    value = null;
    final String sub = value.substring(0, 1);
}

輸出異常信息:

Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String java.lang.String.substring(int, int)' on a null object reference
at com.android.test.demo.exception.TestException.testGHException(TestException.java:112)
at com.android.test.demo.MainActivity.testException(MainActivity.java:64)
at com.android.test.demo.MainActivity.onCreate(MainActivity.java:40)
at android.app.Activity.performCreate(Activity.java:6362)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1122)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2656)

測試2(使用自定義異常):

public void testGHException() {
    try {
        String value = "test";
        value = null;
        final String sub = value.substring(0, 1);
    } catch (NullPointerException e) {
        throw new TestGHException("string value == null", e);
    }
}

輸出異常信息:

Caused by: com.android.test.demo.exception.TestGHException: string value == null
at com.android.test.demo.exception.TestException.testGHException(TestException.java:105)
at com.android.test.demo.MainActivity.testException(MainActivity.java:64)
at com.android.test.demo.MainActivity.onCreate(MainActivity.java:40)
at android.app.Activity.performCreate(Activity.java:6362)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1122)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2656)

結(jié)論:通過比較上面兩個測試例子可以看出,自定義異常使得異常堆棧信息更加直觀吏砂,一樣就能看到出現(xiàn)問題的地方撵儿。

  1. 可以實現(xiàn)異常在受檢異常和運行時異常之間互相轉(zhuǎn)換。有時候引用的某些API拋出了運行時異常(由于不需要編譯器檢查狐血,卻又有可能異常)淀歇,為了具體業(yè)務(wù)的需要,可以在該API上再包一層將這個運行時異常轉(zhuǎn)換成受檢異常匈织,以便提醒調(diào)用者注意該異常浪默,或者反之,簡化繁瑣的受檢異常缀匕,典型的用例是jOOR中對反射接口的受檢異常(ClassNotFoundException纳决、NoSuchMethodException、NoSuchFieldException等)統(tǒng)一轉(zhuǎn)化成ReflectException定義的運行時異常乡小。

jOOR代碼:

//ReflectException自定義異常
public class ReflectException extends RuntimeException {
}

//函數(shù)執(zhí)行接口岳链,
public Reflect call(String name, Object... args) throws ReflectException {
    Class<?>[] types = types(args);

    // Try invoking the "canonical" method, i.e. the one with exact
    // matching argument types
    try {
        Method method = exactMethod(name, types);
        return on(method, object, args);
    }

    // If there is no exact match, try to find a method that has a "similar"
    // signature if primitive argument types are converted to their wrappers
    catch (NoSuchMethodException e) {
        try {
            Method method = similarMethod(name, types);
            return on(method, object, args);
        } catch (NoSuchMethodException e1) {
            throw new ReflectException(e1); //此處將受檢異常轉(zhuǎn)換成了運行時異常
        }
    }
}

//調(diào)用時 ,省去了檢查操作
public static boolean isSplitMode(Context context) {
    return Reflect.on("meizu.splitmode.FlymeSplitModeManager")
        .call("getInstance", context)
        .call("isSplitMode").get();
}

常見影響app的崩潰率指標的異常分析

準備在單獨章節(jié)中持續(xù)更新劲件。

總結(jié)

有經(jīng)驗的的開發(fā)人員都知道掸哑,調(diào)試程序的最大難點不在于修復(fù)bug约急,而在于從海量的代碼中找出bug的藏身之處。因此苗分,我們要做的就是在寫代碼過程中盡可能的暴露bug的可能出處厌蔽。

在《Effective Java》中對異常的使用給出了以下指導(dǎo)原則:

  • 不要將異常處理用于正常的控制流(設(shè)計良好的API不應(yīng)該強迫它的調(diào)用者為了正常的控制流而使用異常)
  • 對可以恢復(fù)的情況使用受檢異常,對編程錯誤使用運行時異常
  • 避免不必要的使用受檢異常(可以通過一些狀態(tài)檢測手段來避免異常的發(fā)生)
  • 優(yōu)先使用標準的異常
  • 每個方法拋出的異常都要有文檔
  • 保持異常的原子性
  • 不要在catch中忽略掉捕獲到的異常*

參考文檔

http://www.importnew.com/1701.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末摔癣,一起剝皮案震驚了整個濱河市奴饮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌择浊,老刑警劉巖戴卜,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異琢岩,居然都是意外死亡投剥,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門担孔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來江锨,“玉大人,你說我怎么就攤上這事糕篇∽挠” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵拌消,是天一觀的道長挑豌。 經(jīng)常有香客問我,道長墩崩,這世上最難降的妖魔是什么氓英? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮泰鸡,結(jié)果婚禮上债蓝,老公的妹妹穿的比我還像新娘。我一直安慰自己盛龄,他們只是感情好饰迹,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著余舶,像睡著了一般啊鸭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上匿值,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天赠制,我揣著相機與錄音,去河邊找鬼。 笑死钟些,一個胖子當著我的面吹牛烟号,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播政恍,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼汪拥,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了篙耗?” 一聲冷哼從身側(cè)響起迫筑,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宗弯,沒想到半個月后脯燃,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡蒙保,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年辕棚,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片追他。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡坟募,死狀恐怖岛蚤,靈堂內(nèi)的尸體忽然破棺而出邑狸,到底是詐尸還是另有隱情,我是刑警寧澤涤妒,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布单雾,位于F島的核電站,受9級特大地震影響她紫,放射性物質(zhì)發(fā)生泄漏硅堆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一贿讹、第九天 我趴在偏房一處隱蔽的房頂上張望渐逃。 院中可真熱鬧,春花似錦民褂、人聲如沸茄菊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽面殖。三九已至,卻和暖如春哭廉,著一層夾襖步出監(jiān)牢的瞬間脊僚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工遵绰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辽幌,地道東北人增淹。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像乌企,于是被迫代替她去往敵國和親埠通。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容

  • 八逛犹、深入理解java異常處理機制 引子try…catch…finally恐怕是大家再熟悉不過的語句了端辱, 你的答案是...
    壹點零閱讀 1,530評論 0 0
  • 通俗編程——白話JAVA異常機制 - 代碼之道,編程之法 - 博客頻道 - CSDN.NEThttp://blog...
    葡萄喃喃囈語閱讀 3,165評論 0 25
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法虽画,類相關(guān)的語法舞蔽,內(nèi)部類的語法,繼承相關(guān)的語法码撰,異常的語法渗柿,線程的語...
    子非魚_t_閱讀 31,587評論 18 399
  • JAVA異常與異常處理詳解 一、異常簡介 什么是異常脖岛? 異常就是有異于常態(tài)朵栖,和正常情況不一樣,有錯誤出錯柴梆。在jav...
    java大濕兄閱讀 992評論 0 11
  • Java異常類型 所有異常類型都是Throwable的子類陨溅,Throwable把異常分成兩個不同分支的子類Erro...
    予別她閱讀 919評論 0 2