亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

Java 異常處理

senntyou / 1348人閱讀

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

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

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

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

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

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

throw用于拋出一個實際的異常,throw可以多帶帶作為語句使用,拋出一個具體的異常對象

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

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

Java異常機制

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

使用try...catch捕獲異常

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

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

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

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

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

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

Java異常捕獲流程示意圖

當(dāng)程序進入負責(zé)異常處理的catch塊時,系統(tǒng)生成的異常對象ex將會傳給catch塊后的異常形參,從而允許catch塊通過該對象來獲得異常的詳細信息

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

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

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

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

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

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

Error錯誤,一般是指與虛擬機(JVM)相關(guān)的問題,如系統(tǒng)崩潰、虛擬機出錯誤、動態(tài)鏈接失敗等,這種錯誤是java程序的根本運行環(huán)境出現(xiàn)了問題,這樣錯誤無法恢復(fù)或不可能捕獲,將導(dǎo)致應(yīng)用程序中斷。通常應(yīng)用程序無法處理這些錯誤,因此應(yīng)用程序不應(yīng)該試圖使用catch塊來捕獲Error對象。在定義該方法時,也無須在其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("您輸入的兩個數(shù)相除的結(jié)果是:" + c );
        }
        catch (IndexOutOfBoundsException ie)
        {
            System.out.println("數(shù)組越界:運行程序時輸入的參數(shù)個數(shù)不夠");
        }
        catch (NumberFormatException ne)
        {
            System.out.println("數(shù)字格式異常:程序只能接受整數(shù)參數(shù)");
        }
        catch (ArithmeticException ae)
        {
            System.out.println("算術(shù)異常");
        }
        catch (Exception e)
        {
            System.out.println("未知異常");
        }
    }
}

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

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

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

如果運行該程序時出現(xiàn)其他異常,該異常對象總是Exception類或其子類的實例,Java運行時將調(diào)用Exception對應(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("未知異常");
        }
    }
}

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

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

Java7提供的多異常捕獲

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

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

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

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("您輸入的兩個數(shù)相除的結(jié)果是:" + c );
        }
        catch (IndexOutOfBoundsException | NumberFormatException
            |ArithmeticException ie)
        {
            System.out.println("程序發(fā)生了數(shù)組越界、數(shù)字格式異常、算術(shù)異常之一");
            // 捕捉多異常時,異常變量默認有final修飾,
            // 所以下面代碼有錯:
            ie = new ArithmeticException("test");  // ①
        }
        catch (Exception e)
        {
            System.out.println("未知異常");
            // 捕捉一個類型的異常時,異常變量沒有final修飾
            // 所以下面代碼完全正確。
            e = new RuntimeException("test");    // ②
        }
    }
}
訪問異常信息

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

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

printStackTrace():將該異常的跟蹤棧信息輸出到標準錯誤輸出

printStackTrace(PrintStream s):將該異常的跟蹤棧信息輸出到標準錯誤輸出

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回收資源

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

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

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

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

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

異常處理語法結(jié)構(gòu)中,只有try塊是必須的,也就是說,如果沒有try塊,則不能有后面的catch塊和finally塊;catch塊和finally塊都是可選的,但catch塊和finally塊至少出現(xiàn)其中之一,也可以同時出現(xiàn);可以有多個catch塊,捕獲父類異常的catch塊必須位于捕獲子類異常的后面;但不能只有try塊,既沒有catch塊,也沒有finally塊;多個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語句強制方法返回
            return ;       // ①
            // 使用exit來退出虛擬機
            // System.exit(1);     // ②
        }
        finally
        {
            // 關(guān)閉磁盤文件,回收資源
            if (fis != null)
            {
                try
                {
                    fis.close();
                }
                catch (IOException ioe)
                {
                    ioe.printStackTrace();
                }
            }
            System.out.println("執(zhí)行finally塊里的資源回收!");
        }
    }
}

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

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

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

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

異常處理的嵌套

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

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

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

Java7的自動關(guān)閉資源的try語句

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

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

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

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

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

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

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

自動關(guān)閉資源的try語句后也可以帶多個catch塊和一個finally塊

Checked異常和Runtime異常體系

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

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

對于Checked異常的處理方式有兩種

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

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

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

使用throws聲明拋出異常

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

throws聲明拋出只能在方法簽名中進行使用,throws可以聲明拋出多個異常類,多個異常類之間以逗號隔開

throws Exception1, Exception2...

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

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

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

public class ThrowsTest2
{
    public static void main(String[] args)
        throws Exception
    {
        // 因為test()方法聲明拋出IOException異常,
        // 所以調(diào)用該方法的代碼要么處于try...catch塊中,
        // 要么處于另一個帶throws聲明拋出的方法中。
        test();
    }
    public static void test()throws IOException
    {
        // 因為FileInputStream的構(gòu)造器聲明拋出IOException異常,
        // 所以調(diào)用FileInputStream的代碼要么處于try...catch塊中,
        // 要么處于另一個帶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
{
    // 子類方法聲明拋出了比父類方法更大的異常
    // 所以下面方法出錯
    public void test()throws Exception
    {
    }
}

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

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

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

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

使用throw拋出異常

當(dāng)程序出現(xiàn)錯誤時,系統(tǒng)會自動拋出異常;Java允許程序自行拋出異常,自行拋出異常使用throw語句來完成。

拋出異常

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

throw ExceptionInsantance;

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

如果throw語句拋出的異常是Checked異常,則該throw語句要么處于try塊里,顯式捕獲該異常,要么放在一個帶throws聲明拋出的方法中,即把該異常交給該方法的調(diào)用者處理;如果throw語句拋出的異常是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)用聲明拋出Runtime異常的方法既可以顯式捕獲該異常,
        // 也可不理會該異常
        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異常,既可以顯式捕獲該異常
            // 也可完全不理會該異常,把該異常交給該方法調(diào)用者處理
            throw new RuntimeException("a的值大于0,不符合要求");
        }
    }
}

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

自定義異常類

在通常情況下,程序很少會自行拋出系統(tǒng)異常,因為異常的類名通常也包含了該異常的有用信息。所以在選擇拋出異常時,應(yīng)該選擇合適的異常類,從而可以明確地描述該異常情況。在這種情形下,應(yīng)用程序常常需要拋出自定義異常

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

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

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

catch和throw同時使用

兩種異常處理方式

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

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

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

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

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

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

應(yīng)用后臺需要通過日志來記錄異常發(fā)生的詳細情況

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

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

異常鏈

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

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

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

這種把捕獲一個異常然后接著拋出另一個異常,并把原始異常信息保存下來是一種典型的鏈式處理(職責(zé)鏈模式),也稱為“異常鏈”

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

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

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

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

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

從結(jié)果可知,異常從thirdMethod方法開始觸發(fā),傳到secondMethod方法,再傳到firstMethod方法,最后傳到main方法,在main方法終止,這個過程就是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)

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

跟蹤棧記錄程序中所有的異常發(fā)生點,各行顯式被調(diào)用方法中執(zhí)行的停止位置,并標明類、類中的方法名、與故障點對應(yīng)的文件的行。一行行地往下看,跟蹤棧總是最內(nèi)部的被調(diào)用方法逐漸上傳,直到最外部業(yè)務(wù)操作的起點,通常就是程序的入口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異常,這個異常的源頭是ThreadExceptionTest的secondMethod方法。這個異常傳播到Thread類的run方法就會結(jié)束(如果該異常沒有得到處理,將會導(dǎo)致該線程中止運行)

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é)束異?;顒?/p>

不要過度使用異常

過度使用異常主要有兩個方面:

把異常和普通錯誤混淆在一起,不再編寫任何錯誤處理代碼,而是以簡單地拋出異常來代替所有的錯誤處理

使用異常處理來代替流程控制

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

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

不要使用過于龐大的try塊

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

避免使用Catch All語句

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

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

這種處理方式有如下兩點不足之處:

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

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

不要忽略捕獲到的異常

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

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

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

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

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

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/66440.html

相關(guān)文章

  • 16.java異常處理

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

    asce1885 評論0 收藏0
  • Java異常處理

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

    CarlBenjamin 評論0 收藏0
  • Java異常處理

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

    Fourierr 評論0 收藏0
  • java異常處理機制的理解

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

    khs1994 評論0 收藏0
  • Java異常處理 10 個最佳實踐

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

    Forelax 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<