Java中的異常(下)

兩類異常

Java中的異常繼承體系為:

異常類的繼承體系

這里的異常( Exception)分為兩大類:Checked異常和Runtime異常岸夯。所有的RuntimeException類及其子類的實例被稱為Runtime異常敌土;不是RuntimeException類及其子類的實例則被稱為Checked異常飘弧。
Java認為Checked異常都是可以被處理(修復)的異常竿裂,所有Java程序必須顯式處理Checked異常。如果程序沒有處理Checked異常冷冗,則該程序無法通過編譯(這是Checked異常與Runtime異常最大的不同)若厚。
Checked異常體現(xiàn)了Java的設計哲學——沒有完善處理的代碼根本不會被執(zhí)行!
對Checked異常的處理有兩種方式:

  • 如果當前方法明確知道如何處理該異常迫靖,程序應該使用try...catch塊來捕獲異常院峡,然后在對應的catch塊中處理該異常;
  • 如果當前方法不知道如何處理該異常系宜,則在定義方法時必須聲明拋出異常照激。

提示:在Eclipse中,可以使用快捷鍵對Checked異常進行處理蜈首,即選擇try...catch塊來捕獲異呈德眨或者throws聲明拋出異常欠母。

使用throws聲明拋出異常

使用throws聲明拋出異常的思路為:當前方法不知道如何處理該異常欢策,該異常應該由上級調(diào)用者處理吆寨;如果main方法也不知道如何處理該異常,也可以使用throws聲明拋出異常踩寇,將該異常交給JVM處理啄清。JVM處理異常的方法則簡單粗暴得多:打印異常的跟蹤棧信息,并中止程序運行俺孙。
throws聲明拋出只能在定義方法時使用辣卒,如果要拋出多個異常類,多個異常類用逗號隔開:

throws ExceptionClass1, ExceptionClass2...

下面是一個簡單的例子:

public class ThrowTest {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("a.txt");
    }
}

由于程序所在的目錄中沒有文件“a.txt”睛榄,而且main方法使用throws聲明拋出異常荣茫,將異常交給JVM處理,所以程序遇到了異常后便打印異常的跟蹤棧信息场靴,并結(jié)束程序:

異常的跟蹤棧信息1

如果某段代碼中調(diào)用了一個帶throws聲明拋出的方法啡莉,該方法聲明拋出了Checked異常,則表明該方法希望它的調(diào)用者來處理該異常旨剥。也就是說咧欣,調(diào)用該方法時要么放在try塊中顯式捕獲該異常,要么放在另一個帶throws聲明拋出的方法中轨帜∑枪荆看下面的例子:

public class ThrowTest2 {
    public static void main(String[] args) throws Exception {
        test();
    }

    public static void test() throws IOException {
        FileInputStream fis = new FileInputStream("a.txt");
    }
}

對兩個throws聲明拋出的解釋是:因為test()方法聲明拋出IOException異常,所以調(diào)用該方法的代碼要么處于try...catch塊中蚌父,要么處于另一個帶throws聲明拋出的方法中哮兰;因為FileInputStream的構(gòu)造器聲明拋出IOException異常,所以調(diào)用該方法的代碼要么處于try...catch塊中苟弛,要么處于另一個帶throws聲明拋出的方法中喝滞。
使用throws聲明拋出異常時同樣有限制:

  • 子類方法聲明拋出的異常應該是父類方法聲明拋出的異常類型的子類或相同;
  • 子類方法聲明拋出的異常不允許比父類方法聲明拋出的異常多嗡午。

Checked異常的優(yōu)劣

Checked異常有兩大不便之處:

  • 對于Checked異常囤躁,Java程序的處理更加復雜,增加了編程的復雜度荔睹;
  • 如果在方法中顯示聲明拋出Checked異常狸演,將會導致方法簽名與異常耦合,如果該方法是重寫父類方法僻他,則該方法拋出的異常還會受到限制宵距。
    當然,既然Checked異常存在吨拗,必然有其合理性:Checked異常能在編譯時提醒程序員代碼可能存在的問題满哪,提醒程序員必須注意處理該異常婿斥,或者聲明該異常由該方法的調(diào)用者來處理,從而可以避免程序員因為粗心而忘記處理該異常哨鸭。

使用throw拋出異常

異常是一種很“主觀”的說法民宿,很多時候系統(tǒng)是否要拋出異常,可能根據(jù)應用的業(yè)務需求來決定像鸡。這時候如果因與業(yè)務需求不符而產(chǎn)生異常活鹰,只能有程序員自行決定拋出,系統(tǒng)是無法拋出的(因為并不是“客觀”意義上的異常)只估。
throw語句拋出的不是異常實例志群,而是一個異常實例,而且每次只能拋出一個異常實例:

throw ExceptionInstance;

來看下面的例子:

try {
    int b = 0;
    if (b == 0) {
        throw new Exception("除數(shù)不能為零蛔钙!");
    }
} catch (Exception e) {
    System.out.println("除數(shù)不能為零锌云!");
}

當Java程序運行時接收到開發(fā)者自行拋出的異常時,同樣會中止當前的執(zhí)行流吁脱,跳到該異常對應的catch塊桑涎,由catch塊來處理該異常。
throw語句拋出的異常如果是Checked異常豫喧,則該throw語句要么處于try塊里石洗,顯式捕獲該異常,要么放在一個帶throws聲明拋出的方法中紧显,把該異常交給該方法的調(diào)用者處理讲衫;如果是Runtime異常,則該語句無須放在try塊里孵班,也無須放在帶throws聲明拋出的方法中涉兽。程序既可以顯式使用try...catch來捕獲并處理該異常,也可以完全不理會該異常篙程,把它交給該方法的調(diào)用者處理枷畏。來看下面的例子:

public class ThrowTest {
    public static void main(String[] args) {
        try {
            //調(diào)用聲明拋出Checked異常的方法
            //要么顯式捕獲該異常
            //要么在main方法中再次聲明拋出
            throwChecked(3);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        //調(diào)用聲明拋出Checked異常的方法
        //既可以顯式捕獲該異常
        //也可以不理會該異常
        throwRuntime(4);
    }

    public static void throwChecked(int a) throws Exception {
        if (a > 0) {
            throw new Exception("a的值大于0,不符合要求虱饿!");
        }
    }

    public static void throwRuntime(int a) {
        if (a > 0) {
            throw new RuntimeException("a的值大于0拥诡,不符合要求!");
        }
    }
}

程序拋出異常:

異常的跟蹤棧信息2

上圖中第一句話是Checked異常的詳細描述氮发,而下面三行紅字則是Runtime異常的跟蹤棧信息渴肉。
注意:throwthrows雖然只相差一個字母,用法卻千差萬別爽冕,使用時一定要注意區(qū)分仇祭!

自定義異常類

通常情況下,程序很少自行拋出系統(tǒng)異常(因為這樣沒有必要)颈畸。在拋出異常時乌奇,應該選擇合適的異常類没讲,從而可以明確地描述該異常情況。在這種情形下礁苗,程序需要拋出自定義異常爬凑。
自定義異常類都應該繼承Exception類,如果希望自定義Runtime異常寂屏,則應該繼承RuntimeException類贰谣。自定義異常類時需要提供兩個構(gòu)造器:一個是無參的構(gòu)造器娜搂,另一個帶一個字符串參數(shù)的構(gòu)造器迁霎,這個字符串作為該異常對象的描述信息(也就是異常對象的getMessage()方法的返回值):

public class AuctionException extends Exception{
    //無參的構(gòu)造器
    public AuctionException(){
    };
    //帶一個字符串參數(shù)的構(gòu)造器
    public  AuctionException(String msg){
        //通過super關(guān)鍵字調(diào)用父類的構(gòu)造器
        //將此字符串參數(shù)傳給異常對象的message屬性
        super(msg);
    }
}

如果要自定義Runtime異常,只需將上面代碼中的父類Exception改為RuntimeException百宇。

catch和throw同時使用

在實際應用中考廉,往往需要對異常采取復雜的處理方式——但一個異常出現(xiàn)時,單靠某個方法無法完全處理該異常携御,必須由幾個方法協(xié)作才可以完全處理該異常昌粤。也就是說,在異常出現(xiàn)的當前方法中啄刹,程序只對異常進行部分處理涮坐,還有些處理需要在該方法的調(diào)用者中才能完成,所以應該再次拋出異常誓军,讓該方法的調(diào)用者也能捕獲到異常袱讹。來看下面的例子:

public class AuctionTest {
    private double initPrice = 3.0;

    public void bid(String bidPrice) throws AuctionException {
        double d = 0.0;
        try {
            d = Double.parseDouble(bidPrice);
        } catch (Exception e) {
            //控制臺打印異常的跟蹤棧信息
            e.printStackTrace();
            //再次拋出自定義異常 
            throw new AuctionException("競拍價必須是數(shù)值,不能含有其他字符昵时!");
        }
        if (initPrice > d) {
            throw new AuctionException("競拍價比起拍價低捷雕,不允許競拍!");
        }
    }

    public static void main(String[] args) {
        AuctionTest at = new AuctionTest();
        try {
            at.bid("df");
        } catch (AuctionException ae) {
            //再次捕獲到bid()方法中的異常壹甥,并對該異常進行處理
            System.err.println(ae.getMessage());
        }
    }
}

程序拋出異常:

異常的跟蹤棧信息3

提示:這種catchthrow結(jié)合使用的情況在大型企業(yè)級應用中非常常用救巷。企業(yè)級應用對異常的處理通常分成兩部分:應用后臺需要通過日志來記錄異常發(fā)生的詳細情況;應用還需要根據(jù)異常向應用使用者傳達某種提示句柠。在這種情形下浦译,所有異常都需要兩個方法共同完成,也就必須將catchthrow結(jié)合使用溯职。

Java的異常跟蹤棧

異常對象的printStackTrace()方法用于打印異常的跟蹤棧信息精盅,根據(jù)printStackTrace()方法的輸出結(jié)果,開發(fā)者可以找到異常的源頭缸榄,并跟蹤到異常一路觸發(fā)的過程渤弛。來看下面的例子:

class SelfException extends RuntimeException {
    SelfException() {
    };

    SelfException(String msg) {
        super(msg);
    }
}

public class PrintStackTraceTest {
    public static void main(String[] args) {
        firstMethod();
    }

    public static void firstMethod() {
        secondMethod();
    }

    public static void secondMethod() {
        thirdMethod();
    }

    public static void thirdMethod() {
        throw new SelfException("自定義異常信息");
    }
}

程序拋出異常:

異常的跟蹤棧信息4

由上圖可知,異常從thirdMethod方法開始觸發(fā)甚带,開始傳到secondMethod方法她肯,再傳到firstMethod方法佳头,最后傳到main方法,在main方法終止晴氨,這個過程就是Java的異常跟蹤棧康嘉。
異常的傳播規(guī)律是:只要異常沒有被完全捕獲(包括異常額沒有被捕獲,或異常處理后重新拋出了新異常)籽前,異常從發(fā)生異常的方法逐漸向外傳播亭珍,首先傳給該方法的調(diào)用者,該方法的調(diào)用者再次傳給其調(diào)用者......直到最后傳給main方法枝哄,如果main方法依然沒有處理該異常肄梨,JVM會中止該程序,并打印異常的跟蹤棧信息挠锥。
上圖的異常跟蹤棧信息十分清晰地記錄了程序執(zhí)行停止的各個點:第一行的信息詳細顯示了異常的類型和詳細信息众羡;接下累跟蹤棧記錄程序中所有的異常發(fā)生點,各行顯示被調(diào)用方法中執(zhí)行的停止位置蓖租,并表明類粱侣、類中的方法名、與故障點對應的文件的行蓖宦。
下面的例子展示了多線程程序中發(fā)生異常的情形:

public class ThreadExceptionTest implements Runnable {
    public void run() {
        firstMethod();
    }

    public void firstMethod() {
        secondMethod();
    }

    public void secondMethod() {
        int a = 5;
        int b = 0;
        int c = a / b;
    }

    public static void main(String[] args) {
        new Thread(new ThreadExceptionTest()).start();
    }
}

程序拋出異常:

異常的跟蹤棧信息5

由上圖可知齐婴,程序在Threadrun方法中出現(xiàn)了ArithmeticException異常,這個異常的源頭是ThreadExceptionTestsecondMethod方法稠茂,這個異常傳播到Thread類的run`方法就會結(jié)束柠偶。

異常處理規(guī)則

  • 不要過度使用異常
    異常只應該用于處理非正常的情況,不要使用異常處理來代替正常的流程控制主慰。對于一些完全剋可預知嚣州、而且處理方式清楚的錯誤,程序應該提供相應的錯誤處理代碼共螺,而不是將其籠統(tǒng)地稱為異常该肴。
  • 不要使用過于龐大的try
    把大塊的try塊分割成多個可能出現(xiàn)異常的程序段落,并把它們放在單獨的try塊中藐不,從而分別捕獲異常匀哄。
  • 避免使用Catch All語句
    所謂Catch All語句指的是一種異常捕獲模塊,它可以處理程序發(fā)生的所有可能異常雏蛮。這種處理方式有兩個弊端:無法對不同的異常分情況處理(如果要分情況處理涎嚼,則需要在catch塊中使用分支語句進行控制,得不償失)挑秉;容易“壓制”異撤ㄌ荩或者忽略“關(guān)鍵”異常。
    Catch All語句:
try{
    //可能引發(fā)Checked異常的代碼
} catch(Throwable t) {
    //進行異常處理
    t.printStackTrace();
}
  • 不要忽略捕獲到的異常
    既然捕獲到了異常,那catch塊就理應做些有用的事情立哑,即對異常采取適當?shù)拇胧禾幚懋惓R共选⒅匦聮伋鲂庐惓;蛘咴诤线m的層處理異常铛绰。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末诈茧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子捂掰,更是在濱河造成了極大的恐慌敢会,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件这嚣,死亡現(xiàn)場離奇詭異鸥昏,居然都是意外死亡,警方通過查閱死者的電腦和手機疤苹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門互广,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人卧土,你說我怎么就攤上這事∠穹” “怎么了尤莺?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長生棍。 經(jīng)常有香客問我颤霎,道長,這世上最難降的妖魔是什么涂滴? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任友酱,我火速辦了婚禮,結(jié)果婚禮上柔纵,老公的妹妹穿的比我還像新娘缔杉。我一直安慰自己跛璧,他們只是感情好掌猛,可當我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著柒竞,像睡著了一般郭计。 火紅的嫁衣襯著肌膚如雪霸琴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天昭伸,我揣著相機與錄音梧乘,去河邊找鬼。 笑死庐杨,一個胖子當著我的面吹牛选调,可吹牛的內(nèi)容都是我干的嗡善。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼学歧,長吁一口氣:“原來是場噩夢啊……” “哼罩引!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起枝笨,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤袁铐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后横浑,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體剔桨,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年徙融,在試婚紗的時候發(fā)現(xiàn)自己被綠了洒缀。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡欺冀,死狀恐怖树绩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情隐轩,我是刑警寧澤饺饭,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站职车,受9級特大地震影響瘫俊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜悴灵,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一扛芽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧积瞒,春花似錦川尖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至银酬,卻和暖如春嘲更,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背揩瞪。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工赋朦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓宠哄,卻偏偏與公主長得像壹将,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子毛嫉,可洞房花燭夜當晚...
    茶點故事閱讀 43,490評論 2 348

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

  • 通俗編程——白話JAVA異常機制 - 代碼之道诽俯,編程之法 - 博客頻道 - CSDN.NEThttp://blog...
    葡萄喃喃囈語閱讀 3,174評論 0 25
  • “郭菁菁,女承粤,一九九零年十月一出生暴区,現(xiàn)年二十五歲。身高一米六三辛臊,體重49公斤仙粱,膚白貌美,本科學歷彻舰,某初中語文老師”...
    六月孺子牛閱讀 428評論 0 4
  • 小朋友的愿望很簡單刃唤,就是有喜歡的東西吃隔心,有喜歡的人愛就足夠了。 無數(shù)心靈健康的小孩都是被“看見”的透揣,被“照顧”的济炎,...
    攝小影閱讀 531評論 2 1
  • 整理瀏覽器的收藏標簽,整理電腦的文件夾辐真,是一件讓人心痛的事。好多東西現(xiàn)在已經(jīng)沒有用了崖堤,放在那里只是占內(nèi)存侍咱,但...
    小輝姑娘閱讀 186評論 0 0
  • 多少贊美欽佩 向那承受風雨 毅然不倒的人 可誰能看到那堅強 背后的傷痛 誰說堅強是美德 哪一個意志的重建 不是從毀...
    花白糖閱讀 229評論 1 3