成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Java 異常處理

senntyou / 1282人閱讀

摘要:下面是異常處理機(jī)制的語(yǔ)法結(jié)構(gòu)業(yè)務(wù)實(shí)現(xiàn)代碼輸入不合法如果執(zhí)行塊里業(yè)務(wù)邏輯代碼時(shí)出現(xiàn)異常,系統(tǒng)自動(dòng)生成一個(gè)異常對(duì)象,該對(duì)象被提交給運(yùn)行時(shí)環(huán)境,這個(gè)過(guò)程被稱為拋出異常。

Java的異常機(jī)制主要依賴于try、catch、finally、throw和throws五個(gè)關(guān)鍵字,

try關(guān)鍵字后緊跟一個(gè)花括號(hào)括起來(lái)的代碼塊(花括號(hào)不可省略),簡(jiǎn)稱try塊,它里面放置可能引發(fā)異常的代碼

catch后對(duì)應(yīng)異常類型和一個(gè)代碼塊,用于表明該catch塊用于處理這種類型的代碼塊

多個(gè)catch塊后還可以跟一個(gè)finally塊,finally塊用于回收在try塊里打開(kāi)的物理資源,異常機(jī)制會(huì)保證finally塊總被執(zhí)行

throws關(guān)鍵字主要在方法簽名中使用,拋出一個(gè)具體的異常對(duì)象

throw用于拋出一個(gè)實(shí)際的異常,throw可以多帶帶作為語(yǔ)句使用,拋出一個(gè)具體的異常對(duì)象

我們希望所有的錯(cuò)誤都可以在編譯階段被發(fā)現(xiàn),就是在試圖運(yùn)行程序之前排除所有錯(cuò)誤,但這是不現(xiàn)實(shí)的,余下的問(wèn)題必須在運(yùn)行期間得到解決。JAVA將異常分為兩種,Checked異常和Runtime異常,JAVA認(rèn)為Checked異常都是可以在編譯階段被處理的異常,所以他強(qiáng)制程序處理所有多的Checked異常;而Runtime異常則無(wú)需處理

異常機(jī)制可以使程序中的異常處理代碼和正常業(yè)務(wù)代碼分離,保證程序代碼更加優(yōu)雅,并可以提高程序的健壯性

Java異常機(jī)制

Java的異常處理機(jī)制可以讓程序具有極好的容錯(cuò)性,讓程序更加健壯。當(dāng)程序運(yùn)行出現(xiàn)意外情形時(shí),系統(tǒng)會(huì)自動(dòng)生成一個(gè)Exception對(duì)象來(lái)通知程序,從而實(shí)現(xiàn)將“業(yè)務(wù)功能實(shí)現(xiàn)代碼”和“錯(cuò)誤處理代碼”分離,提供更好的可讀性

使用try...catch捕獲異常

Java提出了一種假設(shè):如果程序可以順利完成,那就“一切正?!?,把系統(tǒng)的業(yè)務(wù)實(shí)現(xiàn)代碼放在try塊中定義,所有的異常處理邏輯放在catch塊中進(jìn)行處理。下面是Java異常處理機(jī)制的語(yǔ)法結(jié)構(gòu)

try
{
    // 業(yè)務(wù)實(shí)現(xiàn)代碼
    ...
}
catch (Exception)
{
    alert 輸入不合法
    goto retry
}

如果執(zhí)行try塊里業(yè)務(wù)邏輯代碼時(shí)出現(xiàn)異常,系統(tǒng)自動(dòng)生成一個(gè)異常對(duì)象,該對(duì)象被提交給Java運(yùn)行時(shí)環(huán)境,這個(gè)過(guò)程被稱為拋出(throw)異常。當(dāng)Java運(yùn)行時(shí)環(huán)境收到異常對(duì)象時(shí),會(huì)尋找能處理該異常的catch塊,如果找到合適的catch塊,則把該異常對(duì)象交給catch塊處理,這個(gè)過(guò)程被稱為捕獲(catch)異常;如果Java運(yùn)行時(shí)找不到捕獲異常的catch塊,則運(yùn)行時(shí)環(huán)境終止,Java程序也將退出

String inputStr = null;
// br.readLine():每當(dāng)在鍵盤(pán)上輸入一行內(nèi)容按回車,
// 用戶剛剛輸入的內(nèi)容將被br讀取到。
while ((inputStr = br.readLine()) != null)
{
    try
    {
        // 將用戶輸入的字符串以逗號(hào)作為分隔符,分解成2個(gè)字符串
        String[] posStrArr = inputStr.split(",");
        // 將2個(gè)字符串轉(zhuǎn)換成用戶下棋的坐標(biāo)
        int xPos = Integer.parseInt(posStrArr[0]);
        int yPos = Integer.parseInt(posStrArr[1]);
        // 把對(duì)應(yīng)的數(shù)組元素賦為"●"。
        if (!gb.board[xPos - 1][yPos - 1].equals("╋"))
        {
            System.out.println("您輸入的坐標(biāo)點(diǎn)已有棋子了,"
            + "請(qǐng)重新輸入");
            continue;
        }
        gb.board[xPos - 1][yPos - 1] = "●";
    }
    catch (Exception e)
    {
        System.out.println("您輸入的坐標(biāo)不合法,請(qǐng)重新輸入,"
        + "下棋坐標(biāo)應(yīng)以x,y的格式");
        continue;
    }
    ...
}
異常類的繼承體系

當(dāng)Java運(yùn)行時(shí)環(huán)境接收到異常對(duì)象時(shí),catch關(guān)鍵字形式(Exception e)的每一個(gè)catch塊都會(huì)處理該異常類及其實(shí)例

當(dāng)Java運(yùn)行時(shí)環(huán)境接收到異常對(duì)象后,會(huì)依次判斷該異常對(duì)象是否是catch塊后異常類或其子類的實(shí)例,如果是,Java運(yùn)行時(shí)環(huán)境將調(diào)用該catch塊來(lái)處理該異常;否則再次判斷該異常對(duì)象和下一個(gè)catch塊里的異常類進(jìn)行比較

Java異常捕獲流程示意圖

當(dāng)程序進(jìn)入負(fù)責(zé)異常處理的catch塊時(shí),系統(tǒng)生成的異常對(duì)象ex將會(huì)傳給catch塊后的異常形參,從而允許catch塊通過(guò)該對(duì)象來(lái)獲得異常的詳細(xì)信息

try塊后可以有多個(gè)catch塊,try塊后使用多個(gè)catch塊是為了針對(duì)不同異常類提供不同的異常處理方式。當(dāng)系統(tǒng)發(fā)生不同的意外情況時(shí),系統(tǒng)會(huì)生成不同的異常對(duì)象,Java運(yùn)行時(shí)就會(huì)根據(jù)該異常對(duì)象所屬的異常類來(lái)決定使用哪個(gè)catch塊來(lái)處理該異常

通過(guò)在try塊后提供多個(gè)catch塊可以無(wú)須在異常處理塊中使用if、switch判斷異常類型,但依然可以針對(duì)不同異常類型提供相應(yīng)的處理邏輯,從而提供更細(xì)致,更有調(diào)理的異常處理邏輯

從上圖可以看出,通常情況下,如果try塊被執(zhí)行一次,則try塊后只有一個(gè)catch塊會(huì)被執(zhí)行,絕不可能有多個(gè)catch塊被執(zhí)行,除非在循環(huán)中使用了continue開(kāi)始下一次循環(huán),下一次循環(huán)又重新運(yùn)行了try塊,這才可能導(dǎo)致多個(gè)catch塊被執(zhí)行

try塊與if語(yǔ)句不一樣,try塊后的花括號(hào)({...})不可以省略,即使try塊里只有一行代碼,也不可以省略這個(gè)花括號(hào)。與之類似的,catch塊后的花括號(hào)({...})也不可以省略。還有一點(diǎn)需要指出:try塊里聲明的變量是代碼塊內(nèi)局部變量,它只在try塊內(nèi)有效,catch塊中不能訪問(wèn)該變量。

Java常見(jiàn)的異常類之間的繼承關(guān)系圖

Java把所有非正常情況分成兩種:異常(Exception)和錯(cuò)誤(Error),它們都是繼承Throwable父類

Error錯(cuò)誤,一般是指與虛擬機(jī)(JVM)相關(guān)的問(wèn)題,如系統(tǒng)崩潰、虛擬機(jī)出錯(cuò)誤、動(dòng)態(tài)鏈接失敗等,這種錯(cuò)誤是java程序的根本運(yùn)行環(huán)境出現(xiàn)了問(wèn)題,這樣錯(cuò)誤無(wú)法恢復(fù)或不可能捕獲,將導(dǎo)致應(yīng)用程序中斷。通常應(yīng)用程序無(wú)法處理這些錯(cuò)誤,因此應(yīng)用程序不應(yīng)該試圖使用catch塊來(lái)捕獲Error對(duì)象。在定義該方法時(shí),也無(wú)須在其throws子句聲明該方法能拋出Error及其任何子類

public class DivTest
{
    public static void main(String[] args)
    {
        try
        {
            int a = Integer.parseInt(args[0]);
            int b = Integer.parseInt(args[1]);
            int c = a / b;
            System.out.println("您輸入的兩個(gè)數(shù)相除的結(jié)果是:" + c );
        }
        catch (IndexOutOfBoundsException ie)
        {
            System.out.println("數(shù)組越界:運(yùn)行程序時(shí)輸入的參數(shù)個(gè)數(shù)不夠");
        }
        catch (NumberFormatException ne)
        {
            System.out.println("數(shù)字格式異常:程序只能接受整數(shù)參數(shù)");
        }
        catch (ArithmeticException ae)
        {
            System.out.println("算術(shù)異常");
        }
        catch (Exception e)
        {
            System.out.println("未知異常");
        }
    }
}

如果運(yùn)行該程序時(shí)輸入的參數(shù)不夠,將會(huì)發(fā)生數(shù)組越界異常,Java運(yùn)行時(shí)將調(diào)用IndexOutOfBoundsException對(duì)應(yīng)的catch塊處理該異常

如果運(yùn)行該程序時(shí)輸入的參數(shù)不是數(shù)字,而是字母,將發(fā)生數(shù)字格式異常,Java運(yùn)行時(shí)將調(diào)用NumberFormatException對(duì)應(yīng)的catch塊處理該異常

如果運(yùn)行該程序時(shí)輸入的第二個(gè)參數(shù)是0,將發(fā)生除0異常,Java運(yùn)行時(shí)將調(diào)用ArithmeticException對(duì)應(yīng)的catch塊處理該異常

如果運(yùn)行該程序時(shí)出現(xiàn)其他異常,該異常對(duì)象總是Exception類或其子類的實(shí)例,Java運(yùn)行時(shí)將調(diào)用Exception對(duì)應(yīng)的catch塊處理該異常

public class NullTest
{
    public static void main(String[] args)
    {
        Date d = null;
        try
        {
            System.out.println(d.after(new Date()));
        }
        catch (NullPointerException ne)
        {
            System.out.println("空指針異常");
        }
        catch(Exception e)
        {
            System.out.println("未知異常");
        }
    }
}

如果運(yùn)行該程序時(shí)試圖調(diào)用一個(gè)null對(duì)象的實(shí)例方法或?qū)嵗兞繒r(shí),Java運(yùn)行時(shí)將調(diào)用NullPointerException對(duì)應(yīng)的catch塊處理該異常

實(shí)際上,進(jìn)行異常捕獲時(shí)不僅應(yīng)該把Exception類對(duì)應(yīng)的catch塊放在最后,而且所有父類異常的catch塊都應(yīng)該排在子類異常catch塊的后面(先處理小異常,再處理大異常),否則將出現(xiàn)編譯錯(cuò)誤

Java7提供的多異常捕獲

一個(gè)catch塊可以捕獲多鐘類型的異常,使用一個(gè)catch塊捕獲多鐘類型的異常時(shí)需要注意如下兩個(gè)地方

捕獲多鐘類型的異常時(shí),多鐘異常之間用豎線(|)隔開(kāi)

捕獲多鐘類型的異常時(shí),異常變量有隱式的final修飾,因此程序不能對(duì)異常變量重新賦值

public class MultiExceptionTest
{
    public static void main(String[] args)
    {
        try
        {
            int a = Integer.parseInt(args[0]);
            int b = Integer.parseInt(args[1]);
            int c = a / b;
            System.out.println("您輸入的兩個(gè)數(shù)相除的結(jié)果是:" + c );
        }
        catch (IndexOutOfBoundsException | NumberFormatException
            |ArithmeticException ie)
        {
            System.out.println("程序發(fā)生了數(shù)組越界、數(shù)字格式異常、算術(shù)異常之一");
            // 捕捉多異常時(shí),異常變量默認(rèn)有final修飾,
            // 所以下面代碼有錯(cuò):
            ie = new ArithmeticException("test");  // ①
        }
        catch (Exception e)
        {
            System.out.println("未知異常");
            // 捕捉一個(gè)類型的異常時(shí),異常變量沒(méi)有final修飾
            // 所以下面代碼完全正確。
            e = new RuntimeException("test");    // ②
        }
    }
}
訪問(wèn)異常信息

如果程序需要在catch塊中訪問(wèn)異常對(duì)象的相關(guān)信息,則可以通過(guò)訪問(wèn)catch塊的后異常形參來(lái)獲得。當(dāng)Java運(yùn)行時(shí)決定調(diào)用某個(gè)catch塊來(lái)處理該異常對(duì)象時(shí),會(huì)將異常對(duì)象賦給catch塊后的異常參數(shù),程序即可通過(guò)該參數(shù)來(lái)獲得異常相關(guān)信息

getMessage():返回該異常的詳細(xì)描述字符串

printStackTrace():將該異常的跟蹤棧信息輸出到標(biāo)準(zhǔn)錯(cuò)誤輸出

printStackTrace(PrintStream s):將該異常的跟蹤棧信息輸出到標(biāo)準(zhǔn)錯(cuò)誤輸出

getStackTrace():返回該異常的跟蹤棧信息

public class AccessExceptionMsg
{
    public static void main(String[] args) 
    {
        try 
        {
            FileInputStream fileInputStream =new FileInputStream("NBA.txt");
        } 
        catch (Exception ioe) 
        {
            System.out.println(ioe.getMessage());
            ioe.printStackTrace();
        }
    }
}
使用finally回收資源

有些時(shí)候,程序在try塊里打開(kāi)了一些物理資源(例如數(shù)據(jù)庫(kù)連接、網(wǎng)絡(luò)連接和磁盤(pán)文件等),這些物理資源都必須顯式回收

Java垃圾回收機(jī)制不會(huì)回收任何物理資源,垃圾回收機(jī)制只能回收堆內(nèi)存中對(duì)象所占用的內(nèi)存

如果try塊的某條語(yǔ)句引起了異常,該語(yǔ)句后面的其他語(yǔ)句通常不會(huì)獲得執(zhí)行的機(jī)會(huì),這將導(dǎo)致位于該語(yǔ)句之后的資源回收語(yǔ)句得不到執(zhí)行。如果在catch塊里進(jìn)行資源回收,但catch塊完全有可能得不到執(zhí)行,這將導(dǎo)致不能及時(shí)回收這些物理資源

為了保證一定能回收try塊中打開(kāi)的物理資源,異常處理機(jī)制提供了finally塊。不管try塊中的代碼是否出現(xiàn)異常,也不管哪一個(gè)catch塊被執(zhí)行,甚至在try塊或catch塊中執(zhí)行了return語(yǔ)句,finally塊總會(huì)被執(zhí)行

try
{
    // 業(yè)務(wù)實(shí)現(xiàn)代碼
    ...
}
catch (SubException1 e)
{
    // 異常處理塊1
    ...
}
catch (SubException2 e)
{
    // 異常處理塊2
    ...
}
...
finally
{
    // 資源回收塊
    ...
}

異常處理語(yǔ)法結(jié)構(gòu)中,只有try塊是必須的,也就是說(shuō),如果沒(méi)有try塊,則不能有后面的catch塊和finally塊;catch塊和finally塊都是可選的,但catch塊和finally塊至少出現(xiàn)其中之一,也可以同時(shí)出現(xiàn);可以有多個(gè)catch塊,捕獲父類異常的catch塊必須位于捕獲子類異常的后面;但不能只有try塊,既沒(méi)有catch塊,也沒(méi)有finally塊;多個(gè)catch塊必須位于try塊之后,finally塊必須位于所有的catch塊之后

public class FinallyTest
{
    public static void main(String[] args)
    {
        FileInputStream fis = null;
        try
        {
            fis = new FileInputStream("a.txt");
        }
        catch (IOException ioe)
        {
            System.out.println(ioe.getMessage());
            // return語(yǔ)句強(qiáng)制方法返回
            return ;       // ①
            // 使用exit來(lái)退出虛擬機(jī)
            // System.exit(1);     // ②
        }
        finally
        {
            // 關(guān)閉磁盤(pán)文件,回收資源
            if (fis != null)
            {
                try
                {
                    fis.close();
                }
                catch (IOException ioe)
                {
                    ioe.printStackTrace();
                }
            }
            System.out.println("執(zhí)行finally塊里的資源回收!");
        }
    }
}

除非在try塊、catch塊中調(diào)用了退出虛擬機(jī)的方法,否則不管在try塊、catch塊中執(zhí)行怎樣的代碼,出現(xiàn)怎樣的情況,異常處理的finally塊總會(huì)被執(zhí)行

在通常情況下,不要在finally塊中使用如return或throw等導(dǎo)致方法終止的語(yǔ)句,一旦在finally塊中使用了return或throw語(yǔ)句,將會(huì)導(dǎo)致try塊、catch塊中的return、throw語(yǔ)句失效

當(dāng)Java程序執(zhí)行try塊,catch塊時(shí)遇到了return或throw語(yǔ)句,這兩個(gè)語(yǔ)句都會(huì)導(dǎo)致該方法立即結(jié)束,但是系統(tǒng)執(zhí)行這兩個(gè)語(yǔ)句并不會(huì)結(jié)束該方法,而是去尋找該異常處理流程中是否包含finally塊,如果沒(méi)有finally塊,程序立即執(zhí)行return或throw語(yǔ)句,方法終止;如果有finally塊,系統(tǒng)立即開(kāi)始執(zhí)行finally塊——只有當(dāng)finally塊執(zhí)行完后,系統(tǒng)才會(huì)再跳回來(lái)執(zhí)行try塊,catch塊里的return或throw語(yǔ)句。如果finally塊也使用了return或throw等導(dǎo)致方法終止的語(yǔ)句,finally塊已經(jīng)終止了方法,系統(tǒng)將不會(huì)再跳回去執(zhí)行try塊、catch塊里的任何代碼

盡量避免在finally里使用return或throw等導(dǎo)致方法終止的語(yǔ)句,否則可能出現(xiàn)一些很奇怪的情況

異常處理的嵌套

在try塊、catch塊或finally塊中包含完整的異常處理流程的情形被稱為異常處理的嵌套

異常處理流程代碼可以放在任何可執(zhí)行性代碼的地方,因此完整的異常處理流程既可以放在try塊里,也可以放在catch塊里,還可以放在finally塊里

異常處理嵌套的深度沒(méi)有很明確的限制,但通常沒(méi)有必要使用超過(guò)兩層的嵌套異常處理,層次太深的嵌套異常處理沒(méi)有太大必要,而且導(dǎo)致程序可讀性降低

Java7的自動(dòng)關(guān)閉資源的try語(yǔ)句

Java7增強(qiáng)了try語(yǔ)句的功能,它允許在try關(guān)鍵字后緊跟一對(duì)圓括號(hào),圓括號(hào)可以聲明、初始化一個(gè)或多個(gè)資源,此處的資源指的是那些必須在程序結(jié)束時(shí)顯式關(guān)閉的資源(比如數(shù)據(jù)庫(kù)連接、網(wǎng)絡(luò)連接等),try語(yǔ)句在該語(yǔ)句結(jié)束時(shí)自動(dòng)關(guān)閉這些資源。為了保證try語(yǔ)句可以正常關(guān)閉資源,這些資源實(shí)現(xiàn)類必須實(shí)現(xiàn)Closeable或AutoCloseable接口,實(shí)現(xiàn)這些類就必須實(shí)現(xiàn)close()方法

Closeable是AutoCloseable的子接口,可以被自動(dòng)關(guān)閉的資源類要么實(shí)現(xiàn)AutoCloseable接口,要么實(shí)現(xiàn)Closeable接口。

Closeable接口里的close()方法聲明拋出了IOException,因此它的實(shí)現(xiàn)類在實(shí)現(xiàn)close()方法時(shí)只能聲明拋出IOException或其子類

AutoCloseable接口里的close()方法聲明拋出了Exception,因此它的實(shí)現(xiàn)類在實(shí)現(xiàn)close()方法時(shí)可以聲明拋出任何異常

下面程序示范如何使用自動(dòng)關(guān)閉資源的try語(yǔ)句

public class AutoCloseTest
{
    public static void main(String[] args)
        throws IOException
    {
        try (
            // 聲明、初始化兩個(gè)可關(guān)閉的資源
            // try語(yǔ)句會(huì)自動(dòng)關(guān)閉這兩個(gè)資源。
            BufferedReader br = new BufferedReader(
                new FileReader("AutoCloseTest.java"));
            PrintStream ps = new PrintStream(new
                FileOutputStream("a.txt")))
        {
            // 使用兩個(gè)資源
            System.out.println(br.readLine());
            ps.println("莊生曉夢(mèng)迷蝴蝶");
        }
    }
}

上面程序圓括號(hào)里代碼分別聲明、初始化了兩個(gè)IO流,由于BufferedReader、PrintStream都實(shí)現(xiàn)了Closeable接口,而且它們放在try語(yǔ)句中聲明、初始化,所以try語(yǔ)句會(huì)自動(dòng)關(guān)閉它們。因此程序是安全的。自動(dòng)關(guān)閉資源的try語(yǔ)句相當(dāng)于包含了隱式的finally塊(這個(gè)finally塊用于關(guān)閉資源),因此這個(gè)try語(yǔ)句可以既沒(méi)有catch塊,也沒(méi)有finally塊

自動(dòng)關(guān)閉資源的try語(yǔ)句后也可以帶多個(gè)catch塊和一個(gè)finally塊

Checked異常和Runtime異常體系

Java的異常被分為兩大類:Checked異常和Runtime異常(運(yùn)行時(shí)異常)。所有RuntimeException類及其子類實(shí)例被稱為Runtime異常;不是RuntimeException類及其子類的異常實(shí)例則稱為Checked異常

只有Java語(yǔ)言提供了Checked異常,其他語(yǔ)言都沒(méi)有提供Checked異常。Java認(rèn)為Checked異常都是可以被處理(修復(fù))的異常,所以Java程序必須顯式處理Checked異常

對(duì)于Checked異常的處理方式有兩種

當(dāng)前方法明確知道如何處理該異常,程序應(yīng)該使用try...catch塊來(lái)捕獲該異常,然后在對(duì)應(yīng)的catch塊中修補(bǔ)該異常

當(dāng)前方法不知道如何處理這種異常,應(yīng)該在定義該方法時(shí)聲明拋出該異常

Runtime異常則更加靈活,Runtime異常無(wú)須顯式聲明拋出,如果程序需要捕捉Runtime異常,也可以使用try...catch塊來(lái)實(shí)現(xiàn)

使用throws聲明拋出異常

使用throws聲明拋出異常的思路是,當(dāng)前方法不知道如何處理這種類型的異常,該異常應(yīng)該由上一級(jí)調(diào)用者處理;如果main方法也不知道如何處理這種類型的異常,也可以使用throws聲明拋出異常,該異常交給JVM處理。JVM對(duì)異常處理的方法是打印異常的跟蹤棧信息,并中止程序運(yùn)行,這就是前面程序在遇到異常后自動(dòng)結(jié)束的原因

throws聲明拋出只能在方法簽名中進(jìn)行使用,throws可以聲明拋出多個(gè)異常類,多個(gè)異常類之間以逗號(hào)隔開(kāi)

throws Exception1, Exception2...

下面使用了throws來(lái)聲明拋出IOException異常,一旦使用throws語(yǔ)句聲明拋出該異常,程序就無(wú)須使用try...catch塊來(lái)捕獲該異常了。程序聲明不處理IOException異常,將該異常交給JVM處理,所以程序一旦遇到該異常,JVM就會(huì)打印該異常的跟蹤棧信息,并結(jié)束程序

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

如果某段代碼中調(diào)用了一個(gè)帶throws聲明的方法,該方法聲明拋出了Checked異常,則表明該方法希望它的調(diào)用者來(lái)處理該異常。也就是說(shuō),調(diào)用該方法時(shí)要么放在try塊中顯式捕獲該異常,要么放在另一個(gè)帶throws聲明拋出的方法中

public class ThrowsTest2
{
    public static void main(String[] args)
        throws Exception
    {
        // 因?yàn)閠est()方法聲明拋出IOException異常,
        // 所以調(diào)用該方法的代碼要么處于try...catch塊中,
        // 要么處于另一個(gè)帶throws聲明拋出的方法中。
        test();
    }
    public static void test()throws IOException
    {
        // 因?yàn)镕ileInputStream的構(gòu)造器聲明拋出IOException異常,
        // 所以調(diào)用FileInputStream的代碼要么處于try...catch塊中,
        // 要么處于另一個(gè)帶throws聲明拋出的方法中。
        FileInputStream fis = new FileInputStream("a.txt");
    }
}

子類方法聲明拋出的異常類型應(yīng)該是父類方法聲明拋出的異常類型的子類或相同,子類方法聲明拋出的異常不允許比父類方法聲明拋出的異常多

public class OverrideThrows
{
    public void test()throws IOException
    {
        FileInputStream fis = new FileInputStream("a.txt");
    }
}
class Sub extends OverrideThrows
{
    // 子類方法聲明拋出了比父類方法更大的異常
    // 所以下面方法出錯(cuò)
    public void test()throws Exception
    {
    }
}

使用Checked異常至少存在如下兩大不便之處

對(duì)于程序中的Checked異常,java要求必須顯式捕獲并處理該異常,或者顯式聲明拋出該異常。這樣就增加了編程復(fù)雜度

如果在方法中顯式聲明拋出Checked異常,將會(huì)導(dǎo)致方法簽名與異常耦合,如果該方法是重寫(xiě)父類的方法,則該方法拋出的異常還會(huì)受到被重寫(xiě)方法所拋出異常的限制

在大部分時(shí)候推薦使用Runtime異常,而不使用Checked異常。尤其當(dāng)程序需要自行拋出異常時(shí),使用Runtime異常更加簡(jiǎn)潔。當(dāng)使用Runtime異常時(shí),程序無(wú)須在方法中聲明拋出Checked異常,一旦發(fā)生了自定義錯(cuò)誤,程序只管拋出Runtime異常即可

使用throw拋出異常

當(dāng)程序出現(xiàn)錯(cuò)誤時(shí),系統(tǒng)會(huì)自動(dòng)拋出異常;Java允許程序自行拋出異常,自行拋出異常使用throw語(yǔ)句來(lái)完成。

拋出異常

throw語(yǔ)句可以多帶帶使用,throw語(yǔ)句拋出的不是異常類,而是一個(gè)異常實(shí)例,而且每次只能拋出一個(gè)異常實(shí)例。throw語(yǔ)句的語(yǔ)法格式如下:

throw ExceptionInsantance;

當(dāng)Java運(yùn)行時(shí)接收到開(kāi)發(fā)者自行拋出的異常時(shí),同樣會(huì)中止當(dāng)前的執(zhí)行流,跳到該異常對(duì)應(yīng)的catch塊,由該catch塊來(lái)處理該異常。也就是說(shuō),不管是系統(tǒng)自動(dòng)拋出的異常,還是程序員手動(dòng)拋出的異常,Java運(yùn)行時(shí)環(huán)境對(duì)異常的處理沒(méi)有任何差別

如果throw語(yǔ)句拋出的異常是Checked異常,則該throw語(yǔ)句要么處于try塊里,顯式捕獲該異常,要么放在一個(gè)帶throws聲明拋出的方法中,即把該異常交給該方法的調(diào)用者處理;如果throw語(yǔ)句拋出的異常是Runtime異常,則該語(yǔ)句無(wú)須放在try塊里,也無(wú)須放在帶throws聲明拋出的方法中;程序既可以顯式使用try…catch來(lái)捕獲并處理該異常,也可以完全不理會(huì)該異常,把該異常交給方法調(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)用聲明拋出Runtime異常的方法既可以顯式捕獲該異常,
        // 也可不理會(huì)該異常
        throwRuntime(3);
    }
    public static void throwChecked(int a)throws Exception
    {
        if (a > 0)
        {
            // 自行拋出Exception異常
            // 該代碼必須處于try塊里,或處于帶throws聲明的方法中
            throw new Exception("a的值大于0,不符合要求");
        }
    }
    public static void throwRuntime(int a)
    {
        if (a > 0)
        {
            // 自行拋出RuntimeException異常,既可以顯式捕獲該異常
            // 也可完全不理會(huì)該異常,把該異常交給該方法調(diào)用者處理
            throw new RuntimeException("a的值大于0,不符合要求");
        }
    }
}

自行拋出Runtime異常比自行拋出Checked異常的靈活性更好。拋出Checked異常則可以讓編譯器提醒程序員必須處理該異常

自定義異常類

在通常情況下,程序很少會(huì)自行拋出系統(tǒng)異常,因?yàn)楫惓5念惷ǔR舶嗽摦惓5挠杏眯畔?。所以在選擇拋出異常時(shí),應(yīng)該選擇合適的異常類,從而可以明確地描述該異常情況。在這種情形下,應(yīng)用程序常常需要拋出自定義異常

自定義異常都應(yīng)該繼承Exception基類,如果希望自定義Runtime異常,則應(yīng)該繼承RuntimeException基類。定義異常類時(shí)通常需要提供兩個(gè)構(gòu)造器:一個(gè)是無(wú)參數(shù)的構(gòu)造器;另一個(gè)是帶一個(gè)字符串參數(shù)的構(gòu)造器,這個(gè)字符串將作為該異常對(duì)象的描述信息(也就是異常對(duì)象的getMessage()方法的返回值)

public class AuctionException extends Exception
{
    // 無(wú)參構(gòu)造器
    public AuctionException(){}
    
    // 帶一個(gè)字符串參數(shù)的構(gòu)造器
    public AuctionException(String msg)
    {
        super(msg);
    }
}

在大部分情況下,創(chuàng)建自定義異常都可采用與AuctionException.java相似的代碼完成,只需改變AuctionException異常的類名即可,讓該異常類的類名可以準(zhǔn)確描述該異常

catch和throw同時(shí)使用

兩種異常處理方式

在出現(xiàn)異常的方法內(nèi)捕獲并處理異常,該方法的調(diào)用者將不能再次捕獲該異常

該方法簽名中聲明拋出該異常,將該異常完全交給方法調(diào)用者處理

當(dāng)一個(gè)異常出現(xiàn)時(shí),單靠某個(gè)方法無(wú)法完全處理該異常,必須由幾個(gè)方法協(xié)作才可以完全處理該異常。也就是說(shuō),在異常出現(xiàn)的當(dāng)前方法中,程序只對(duì)異常進(jìn)行部分處理,還有些處理需要在該方法的調(diào)用者中才能完成,所以應(yīng)該再次拋出異常,讓該方法的調(diào)用者也能捕獲到異常

為了實(shí)現(xiàn)這種通過(guò)多個(gè)方法協(xié)作處理同一異常的情形,可以在catch塊中結(jié)合throw語(yǔ)句來(lái)完成

public class AuctionTest
{
    private double initPrice = 30.0;
    // 因?yàn)樵摲椒ㄖ酗@式拋出了AuctionException異常,
    // 所以此處需要聲明拋出AuctionException異常
    public void bid(String bidPrice)
        throws AuctionException
    {
        double d = 0.0;
        try
        {
            d = Double.parseDouble(bidPrice);
        }
        catch (Exception e)
        {
            // 此處完成本方法中可以對(duì)異常執(zhí)行的修復(fù)處理,
            // 此處僅僅是在控制臺(tái)打印異常跟蹤棧信息。
            e.printStackTrace();
            // 再次拋出自定義異常
            throw new AuctionException("競(jìng)拍價(jià)必須是數(shù)值,"
                + "不能包含其他字符!");
        }
        if (initPrice > d)
        {
            throw new AuctionException("競(jìng)拍價(jià)比起拍價(jià)低,"
                + "不允許競(jìng)拍!");
        }
        initPrice = d;
    }
    public static void main(String[] args)
    {
        AuctionTest at = new AuctionTest();
        try
        {
            at.bid("df");
        }
        catch (AuctionException ae)
        {
            // 再次捕捉到bid方法中的異常。并對(duì)該異常進(jìn)行處理
            System.err.println(ae.getMessage());
        }
    }
}

這種catch和throw結(jié)合使用的情況在大型企業(yè)級(jí)應(yīng)用中非常常用。企業(yè)級(jí)應(yīng)用對(duì)異常的處理通常分成兩個(gè)部

應(yīng)用后臺(tái)需要通過(guò)日志來(lái)記錄異常發(fā)生的詳細(xì)情況

應(yīng)用還需要根據(jù)異常向應(yīng)用傳達(dá)某種提示

在這種情形下,所有異常都需要兩個(gè)方法共同完成,也就必須將catch和throw結(jié)合使用

異常鏈

對(duì)于真實(shí)的企業(yè)級(jí)應(yīng)用而言,常常有嚴(yán)格的分層關(guān)系,層與層之間有非常清晰的劃分,上層功能的實(shí)現(xiàn)嚴(yán)格依賴于下層的API,也不會(huì)跨層訪問(wèn)。下圖顯式了這種具有分層結(jié)構(gòu)應(yīng)用的大致示意圖

程序先捕獲原始的異常,然后拋出一個(gè)新的業(yè)務(wù)異常,新的業(yè)務(wù)異常中包含了對(duì)用戶的提示信息,這種處理方式被稱為異常轉(zhuǎn)譯

public calSal throws SalException
{
    try
    {
        //實(shí)現(xiàn)結(jié)算工資的業(yè)務(wù)邏輯
        ....
    }
    catch(SQLException sqle)
    {
        //把原始異常記錄下來(lái),留個(gè)管理員
        ...
        //下面異常中的message就是向用戶的提示
        throw new SalException("訪問(wèn)底層數(shù)據(jù)庫(kù)出現(xiàn)異常");
    }
    catch(Exception e)
    {
        //把原始異常記錄下來(lái),留個(gè)管理員
        ....
        //下面異常中的message就是向用戶的提示
        throw new SalException("系統(tǒng)出現(xiàn)未知異常");
    }
}

這種把捕獲一個(gè)異常然后接著拋出另一個(gè)異常,并把原始異常信息保存下來(lái)是一種典型的鏈?zhǔn)教幚恚氊?zé)鏈模式),也稱為“異常鏈”

所有的Throwable子類在構(gòu)造器中都可以接受一個(gè)cause對(duì)象作為參數(shù)。這個(gè)cause就用來(lái)表示原始異常,這樣可以把原始異常傳遞給新的異常,使得即使在當(dāng)前位置創(chuàng)建并拋出了新的異常,你也能通過(guò)這個(gè)異常鏈追蹤到異常最初發(fā)生的位置。如果我們希望上面SalException可以追蹤到最原始的異常信息。則可以將該方法改寫(xiě)如下

public calSal throws SalException
{
    try
    {
        //實(shí)現(xiàn)結(jié)算工資的業(yè)務(wù)邏輯
        ....
    }
    catch(SQLException sqle)
    {
        //把原始異常記錄下來(lái),留個(gè)管理員
        ...
        //下面異常中的message就是向用戶的提示
        throw new SalException(sqle);
    }
    catch(Exception e)
    {
        //把原始異常記錄下來(lái),留個(gè)管理員
        ....
        //下面異常中的message就是向用戶的提示
        throw new SalException(e);
    }
}

上面程序中創(chuàng)建SalException對(duì)象時(shí),傳入了一個(gè)Exception對(duì)象,而不是傳入了一個(gè)String對(duì)象,這就需要SalException類有相應(yīng)的構(gòu)造器。從JDK1.4以后,Throwable基類有一個(gè)可以接收Exception參數(shù)的方法,所以可以采用如下代碼來(lái)定義SalException類

public class SalException extends Exception
{
    public SalException(){}
    public SalException(String msg)
    {
        super(msg);
    }
    public SalException(Throwable t)
    {
        super(t);
    }
}

Java的異常跟蹤棧

異常對(duì)象的printStackTrace()方法用于打印異常的跟蹤棧信息,根據(jù)printStackTrace()方法的輸出結(jié)果,開(kāi)發(fā)者可以找到異常的源頭,并跟蹤到異常一路觸發(fā)的過(guò)程

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("自定義異常信息");
    }
}

從結(jié)果可知,異常從thirdMethod方法開(kāi)始觸發(fā),傳到secondMethod方法,再傳到firstMethod方法,最后傳到main方法,在main方法終止,這個(gè)過(guò)程就是Java的異常跟蹤棧

Exception in thread "main" SelfException: 自定義異常信息
at PrintStackTraceTest.thirdMethod(PrintStackTraceTest.java:25)
at PrintStackTraceTest.secondMethod(PrintStackTraceTest.java:21)
at PrintStackTraceTest.firstMethod(PrintStackTraceTest.java:17)
at PrintStackTraceTest.main(PrintStackTraceTest.java:13)

只要異常沒(méi)有被完全捕獲(包括異常沒(méi)有捕獲,或異常被處理后重新拋出了新異常),異常從發(fā)生異常的方法逐漸向外傳播,首先傳給該方法的調(diào)用者,該方法調(diào)用者再次傳給其調(diào)用者...直至最后傳到main方法,如果main方法依然沒(méi)有處理該異常,JVM會(huì)中止該程序,并打印異常的跟蹤棧信息

跟蹤棧記錄程序中所有的異常發(fā)生點(diǎn),各行顯式被調(diào)用方法中執(zhí)行的停止位置,并標(biāo)明類、類中的方法名、與故障點(diǎn)對(duì)應(yīng)的文件的行。一行行地往下看,跟蹤??偸亲顑?nèi)部的被調(diào)用方法逐漸上傳,直到最外部業(yè)務(wù)操作的起點(diǎn),通常就是程序的入口main方法或Thread類的run方法(多線程)

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();
    }
}

程序在Thread的run方法中出現(xiàn)了ArithmeticException異常,這個(gè)異常的源頭是ThreadExceptionTest的secondMethod方法。這個(gè)異常傳播到Thread類的run方法就會(huì)結(jié)束(如果該異常沒(méi)有得到處理,將會(huì)導(dǎo)致該線程中止運(yùn)行)

Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
at ThreadExceptionTest.secondMethod(ThreadExceptionTest.java:15)
at ThreadExceptionTest.firstMethod(ThreadExceptionTest.java:9)
at ThreadExceptionTest.run(ThreadExceptionTest.java:5)
at java.lang.Thread.run(Unknown Source)
異常處理規(guī)則

使程序代碼混亂最小化

捕獲并保留診斷信息

通知合適的人員

采用合適的方式結(jié)束異?;顒?dòng)

不要過(guò)度使用異常

過(guò)度使用異常主要有兩個(gè)方面:

把異常和普通錯(cuò)誤混淆在一起,不再編寫(xiě)任何錯(cuò)誤處理代碼,而是以簡(jiǎn)單地拋出異常來(lái)代替所有的錯(cuò)誤處理

使用異常處理來(lái)代替流程控制

異常處理機(jī)制的初衷是將不可預(yù)期的處理代碼和正常的業(yè)務(wù)邏輯處理代碼分離,因此絕不要使用異常處理來(lái)代替正常的業(yè)務(wù)邏輯判斷。另外,異常機(jī)制的效率比正常的流程控制效率差,所以不要使用異常處理來(lái)代替正常的程序流程控制

異常只應(yīng)該用于處理非正常的情況,不要使用異常處理來(lái)代替正常的流程控制。對(duì)于一些完全可預(yù)知,而且處理方式清楚的錯(cuò)誤,程序應(yīng)該提供相應(yīng)的錯(cuò)誤處理代碼,而不是將其籠統(tǒng)地稱為異常

不要使用過(guò)于龐大的try塊

正確的做法是,把大塊的try塊分割成多個(gè)可能出現(xiàn)異常的程序段落,并把它們放在多帶帶的try塊中,從而分別捕獲并處理異常

避免使用Catch All語(yǔ)句

所謂Catch All語(yǔ)句指的是一種異常捕獲模塊,它可以處理程序發(fā)生的所有可能異常

try
{
       // code here with checked exceptions
}
catch (Throwable t) 
{
       //exception handler
       t.printStackTrace();
}

這種處理方式有如下兩點(diǎn)不足之處:

所有異常都采用相同的處理方式,這將導(dǎo)致無(wú)法對(duì)不同異常分情況處理,如果要分情況處理,則需要在catch塊中使用分支語(yǔ)句進(jìn)行控制,這是得不償失的做法。

這種捕獲方式可能將程序中的錯(cuò)誤、Runtime異常等可能導(dǎo)致程序終止的情況全部捕獲到,從而 “壓制”了異常。如果出現(xiàn)了一些“關(guān)鍵”異常,那個(gè)異常也會(huì)被“靜悄悄”地忽略

不要忽略捕獲到的異常

通常建議對(duì)異常進(jìn)行適當(dāng)措施:

處理異常。對(duì)異常采用合適的修補(bǔ),然后繞過(guò)異常發(fā)生的地方繼續(xù)執(zhí)行;或者用別的數(shù)據(jù)進(jìn)行計(jì)算,以代替期望的方法返回值;或者提示用戶重新操作......總之,對(duì)于Checked異常,程序應(yīng)該盡量采用修復(fù)

重新拋出新異常。把當(dāng)前運(yùn)行環(huán)境下能做的事情盡量作完,然后進(jìn)行異常轉(zhuǎn)譯,把異常包裝成當(dāng)前層的異常,重新拋出給上層調(diào)用者

在合適的層處理異常。如果當(dāng)前層不清楚如何處理異常,就不要在當(dāng)前層使用catch語(yǔ)句來(lái)捕獲該異常,直接使用throws聲明拋出該異常,讓上層調(diào)用者來(lái)負(fù)責(zé)處理該異常

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/66440.html

相關(guān)文章

  • 16.java異常處理

    摘要:不受檢查異常為編譯器不要求強(qiáng)制處理的異常,檢查異常則是編譯器要求必須處置的異常。潛在的異常處理器是異常發(fā)生時(shí)依次存留在調(diào)用棧中的方法的集合。當(dāng)運(yùn)行時(shí)系統(tǒng)遍歷調(diào)用棧而未找到合適的異常處理器,則運(yùn)行時(shí)系統(tǒng)終止。異常處理涉及到五個(gè)關(guān)鍵字,分別是。 概念 程序運(yùn)行時(shí),發(fā)生的不被期望的事件,它阻止了程序按照程序員的預(yù)期正常執(zhí)行,這就是異常。 異常是程序中的一些錯(cuò)誤,但并不是所有的錯(cuò)誤都是異常,并...

    asce1885 評(píng)論0 收藏0
  • Java異常處理

    摘要:異常也就是指程序運(yùn)行時(shí)發(fā)生錯(cuò)誤,而異常處理就是對(duì)這些錯(cuò)誤進(jìn)行處理和控制。有兩個(gè)重要的子類異常和錯(cuò)誤,二者都是異常處理的重要子類,各自都包含大量子類。需要注意的是,一旦某個(gè)捕獲到匹配的異常類型,將進(jìn)入異常處理代碼。 1,異?,F(xiàn)象 程序錯(cuò)誤分為三種:1,編譯錯(cuò)誤;2,運(yùn)行時(shí)錯(cuò)誤;3,邏輯錯(cuò)誤。 編譯錯(cuò)誤是因?yàn)槌绦驔](méi)有遵循語(yǔ)法規(guī)則,編譯程序能夠自己發(fā)現(xiàn)并且提示我們錯(cuò)誤的原因和位置,這...

    CarlBenjamin 評(píng)論0 收藏0
  • Java異常處理

    摘要:可以被異常處理機(jī)制使用,是異常處理的核心。非檢測(cè)異常,在編譯時(shí),不會(huì)提示和發(fā)現(xiàn)異常的存在,不強(qiáng)制要求程序員處理這樣的異常??傮w來(lái)說(shuō),語(yǔ)言的異常處理流程,從程序中獲取異常信息。處理運(yùn)行時(shí)異常,采用邏輯合理規(guī)避同時(shí)輔助處理。 目錄 什么是Java異常? 當(dāng)一個(gè)Exception在程序中發(fā)生的時(shí)候,JVM是怎么做的呢? 當(dāng)我們編寫(xiě)程序的時(shí)候如何對(duì)待可能出現(xiàn)的異常呢? 正文 1. 什么是J...

    Fourierr 評(píng)論0 收藏0
  • java異常處理機(jī)制的理解

    摘要:根據(jù)異常對(duì)象判斷是否存在異常處理。否則,范圍小的異常會(huì)因異常處理完成而無(wú)法處理。異常處理中使用作為異常的統(tǒng)一出口。 參考《第一行代碼java》《java程序設(shè)計(jì)教程》java中程序的錯(cuò)誤有語(yǔ)法錯(cuò)誤、語(yǔ)義錯(cuò)誤。如果是語(yǔ)法性錯(cuò)誤,在編譯時(shí)就可以檢查出來(lái)并解決。語(yǔ)義錯(cuò)誤是在程序運(yùn)行時(shí)出現(xiàn)的,在編譯時(shí)沒(méi)有錯(cuò)誤,但在運(yùn)行時(shí)可能會(huì)出現(xiàn)錯(cuò)誤導(dǎo)致程序退出,這些錯(cuò)誤稱為異常。在沒(méi)有異常處理的情況下,也即...

    khs1994 評(píng)論0 收藏0
  • Java異常處理 10 個(gè)最佳實(shí)踐

    摘要:為可恢復(fù)的錯(cuò)誤使用檢查型異常,為編程錯(cuò)誤使用非檢查型錯(cuò)誤。檢查型異常保證你對(duì)錯(cuò)誤條件提供異常處理代碼,這是一種從語(yǔ)言到強(qiáng)制你編寫(xiě)健壯的代碼的一種方式,但同時(shí)會(huì)引入大量雜亂的代碼并導(dǎo)致其不可讀。在編程中選擇檢查型異常還是運(yùn)行時(shí)異常。 異常處理是Java 開(kāi)發(fā)中的一個(gè)重要部分。它是關(guān)乎每個(gè)應(yīng)用的一個(gè)非功能性需求,是為了處理任何錯(cuò)誤狀況,比如資源不可訪問(wèn),非法輸入,空輸入等等。Java提供了...

    Forelax 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<