溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

java中的異常

發(fā)布時(shí)間:2020-06-21 22:56:31 來(lái)源:億速云 閱讀:150 作者:鴿子 欄目:編程語(yǔ)言

一. 異常的定義

在《java編程思想》中這樣定義 異常:阻止當(dāng)前方法或作用域繼續(xù)執(zhí)行的問(wèn)題。雖然java中有異常處理機(jī)制,但是要明確一點(diǎn),決不應(yīng)該用"正常"的態(tài)度來(lái)看待異常。絕對(duì)一點(diǎn)說(shuō)異常就是某種意義上的錯(cuò)誤,就是問(wèn)題,它可能會(huì)導(dǎo)致程序失敗。之所以java要提出異常處理機(jī)制,就是要告訴開(kāi)發(fā)人員,你的程序出現(xiàn)了不正常的情況,請(qǐng)注意。

記得當(dāng)初學(xué)習(xí)java的時(shí)候,異??偸歉悴惶宄?,不知道這個(gè)異常是什么意思,為什么會(huì)有這個(gè)機(jī)制?但是隨著知識(shí)的積累逐漸也對(duì)異常有一點(diǎn)感覺(jué)了。舉一個(gè)例子來(lái)說(shuō)明一下異常的用途。

public class Calculator {
    public int devide(int num1, int num2) {
        //判斷除數(shù)是否為0
        if(num2 == 0) {
            throw new IllegalArgumentException("除數(shù)不能為零");
        }
         
        return num1/num2;
    }
}

看一下這個(gè)類中關(guān)于除運(yùn)算的方法,如果你是新手你可能會(huì)直接返回計(jì)算結(jié)果,根本不去考慮什么參數(shù)是否正確,是否合法(當(dāng)然可以原諒,誰(shuí)都是這樣過(guò)來(lái)的)。但是我們應(yīng)盡可能的考慮周全,把可能導(dǎo)致程序失敗的"苗頭"扼殺在搖籃中,所以進(jìn)行參數(shù)的合法性檢查就很有必要了。

其中執(zhí)行參數(shù)檢查拋出來(lái)的那個(gè)參數(shù)非法異常,這就屬于這個(gè)方法的不正常情況。正常情況下我們會(huì)正確的使用計(jì)算器,但是不排除粗心大意把除數(shù)賦值為0。如果你之前沒(méi)有考慮到這種情況,并且恰巧用戶數(shù)學(xué)基礎(chǔ)不好,那么你完了。但是如果你之前考慮到了這種情況,那么很顯然錯(cuò)誤已在你的掌控之中。

二. 異常掃盲行動(dòng)

今天和別人聊天時(shí)看到一個(gè)笑話:世界上最真情的相依,是你在try我在catch。無(wú)論你發(fā)神馬脾氣,我都默默承受,靜靜處理。 大多數(shù)新手對(duì)java異常的感覺(jué)就是:try...catch...。沒(méi)錯(cuò),這是用的最多的,也是最實(shí)用的。我的感覺(jué)就是:java異常是從"try...catch..."走來(lái)。

首先來(lái)熟悉一下java的異常體系:

Throwable 類是 Java 語(yǔ)言中所有錯(cuò)誤或異常的超類(這就是一切皆可拋的東西)。它有兩個(gè)子類:Error和Exception。

Error:用于指示合理的應(yīng)用程序不應(yīng)該試圖捕獲的嚴(yán)重問(wèn)題。這種情況是很大的問(wèn)題,大到你不能處理了,所以聽(tīng)之任之就行了,你不用管它。比如說(shuō)VirtualMachineError:當(dāng) Java 虛擬機(jī)崩潰或用盡了它繼續(xù)操作所需的資源時(shí),拋出該錯(cuò)誤。好吧,就算這個(gè)異常的存在了,那么應(yīng)該何時(shí),如何處理它呢??交給JVM吧,沒(méi)有比它更專業(yè)的了。

Exception:它指出了合理的應(yīng)用程序想要捕獲的條件。Exception又分為兩類:一種是CheckedException,一種是UncheckedException。

這兩種Exception的區(qū)別主要是CheckedException需要用try...catch...顯示的捕獲,而UncheckedException不需要捕獲。通常UncheckedException又叫做RuntimeException。

我們常見(jiàn)的RuntimeExcepiton有IllegalArgumentException、IllegalStateException、NullPointerException、IndexOutOfBoundsException等等。對(duì)于那些CheckedException就不勝枚舉了,我們?cè)诰帉?xiě)程序過(guò)程中try...catch...捕捉的異常都是CheckedException。io包中的IOException及其子類,這些都是CheckedException。

三. 異常的使用

在異常的使用這一部分主要是演示代碼,都是我們平常寫(xiě)代碼的過(guò)程中會(huì)遇到的(當(dāng)然只是一小部分),拋磚引玉嗎!

例1. 這個(gè)例子主要通過(guò)兩個(gè)方法對(duì)比來(lái)演示一下有了異常以后代碼的執(zhí)行流程。

public static void testException1() {
        int[] ints = new int[] { 1, 2, 3, 4 };
        System.out.println("異常出現(xiàn)前");
        try {
            System.out.println(ints[4]);
            System.out.println("我還有幸執(zhí)行到嗎");// 發(fā)生異常以后,后面的代碼不能被執(zhí)行
        } catch (IndexOutOfBoundsException e) {
            System.out.println("數(shù)組越界錯(cuò)誤");
        }
        System.out.println("異常出現(xiàn)后");
    }
    /*output:
    異常出現(xiàn)前
    數(shù)組越界錯(cuò)誤
    4
    異常出現(xiàn)后
    */
public static void testException2() {
        int[] ints = new int[] { 1, 2, 3, 4 };
        System.out.println("異常出現(xiàn)前");
        System.out.println(ints[4]);
        System.out.println("我還有幸執(zhí)行到嗎");// 發(fā)生異常以后,他后面的代碼不能被執(zhí)行
    }

首先指出例子中的不足之處,IndexOutofBoundsException是一個(gè)非受檢異常,所以不用try...catch...顯示捕捉,但是我的目的是對(duì)同一個(gè)異常用不同的處理方式,看它會(huì)有什么不同的而結(jié)果(這里也就只能用它將就一下了)。異常出現(xiàn)時(shí)第一個(gè)方法只是跳出了try塊,但是它后面的代碼會(huì)照樣執(zhí)行的。

但是第二種就不一樣了直接跳出了方法,比較強(qiáng)硬。從第一個(gè)方法中我們看到,try...catch...是一種"事務(wù)性"的保障,它的目的是保證程序在異常的情況下運(yùn)行完畢,同時(shí)它還會(huì)告知程序員程序中出錯(cuò)的詳細(xì)信息(這種詳細(xì)信息有時(shí)要依賴于程序員設(shè)計(jì))。

例2. 重新拋出異常

public class Rethrow {
    public static void readFile(String file) throws FileNotFoundException {
        try {
            BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            System.err.println("不知道如何處理該異?;蛘吒静幌胩幚硭?,但是不做處理又不合適,這是重新拋出異常交給上一級(jí)處理");
            //重新拋出異常
            throw e;
        }
    }
     
    public static void printFile(String file) {
        try {
            readFile(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
     
    public static void main(String[] args) {
        printFile("D:/file");
    }
}

異常的本意是好的,讓我們?cè)噲D修復(fù)程序,但是現(xiàn)實(shí)中我們修復(fù)的幾率很小,我們很多時(shí)候就是用它來(lái)記錄出錯(cuò)的信息。如果你厭倦了不停的處理異常,重新拋出異常對(duì)你來(lái)說(shuō)可能是一個(gè)很好的解脫。原封不動(dòng)的把這個(gè)異常拋給上一級(jí),拋給調(diào)用這個(gè)方法的人,讓他來(lái)費(fèi)腦筋吧。這樣看來(lái),java異常(當(dāng)然指的是受檢異常)又給我們平添很多麻煩,盡管它的出發(fā)點(diǎn)是好的。

例3. 異常鏈的使用及異常丟失

定義三個(gè)異常類:ExceptionA,ExceptionB,ExceptionC

public class ExceptionA extends Exception {
    public ExceptionA(String str) {
        super();
    }
}
 
public class ExceptionB extends ExceptionA {
 
    public ExceptionB(String str) {
        super(str);
    }
}
 
public class ExceptionC extends ExceptionA {
    public ExceptionC(String str) {
        super(str);
    }
}

異常丟失的情況:

public class NeverCaught {
    static void f() throws ExceptionB{
        throw new ExceptionB("exception b");
    }
 
    static void g() throws ExceptionC {
        try {
            f();
        } catch (ExceptionB e) {
            ExceptionC c = new ExceptionC("exception a");
            throw c;
        }
    }
 
    public static void main(String[] args) {
            try {
                g();
            } catch (ExceptionC e) {
                e.printStackTrace();
            }
    }
 
}
/*
exception.ExceptionC
at exception.NeverCaught.g(NeverCaught.java:12)
at exception.NeverCaught.main(NeverCaught.java:19)
*/

為什么只是打印出來(lái)了ExceptionC而沒(méi)有打印出ExceptionB呢?這個(gè)還是自己分析一下吧!

上面的情況相當(dāng)于少了一種異常,這在我們排錯(cuò)的過(guò)程中非常的不利。那我們遇到上面的情況應(yīng)該怎么辦呢?這就是異常鏈的用武之地:保存異常信息,在拋出另外一個(gè)異常的同時(shí)不丟失原來(lái)的異常。

public class NeverCaught {
    static void f() throws ExceptionB{
        throw new ExceptionB("exception b");
    }
 
    static void g() throws ExceptionC {
        try {
            f();
        } catch (ExceptionB e) {
            ExceptionC c = new ExceptionC("exception a");
            //異常連
            c.initCause(e);
            throw c;
        }
    }
 
    public static void main(String[] args) {
            try {
                g();
            } catch (ExceptionC e) {
                e.printStackTrace();
            }
    }
 
}
/*
exception.ExceptionC
at exception.NeverCaught.g(NeverCaught.java:12)
at exception.NeverCaught.main(NeverCaught.java:21)
Caused by: exception.ExceptionB
at exception.NeverCaught.f(NeverCaught.java:5)
at exception.NeverCaught.g(NeverCaught.java:10)
... 1 more
*/

這個(gè)異常鏈的特性是所有異常均具備的,因?yàn)檫@個(gè)initCause()方法是從Throwable繼承的。

例4. 清理工作

清理工作對(duì)于我們來(lái)說(shuō)是必不可少的,因?yàn)槿绻恍┫馁Y源的操作,比如IO,JDBC。如果我們用完以后沒(méi)有及時(shí)正確的關(guān)閉,那后果會(huì)很嚴(yán)重,這意味著內(nèi)存泄露。異常的出現(xiàn)要求我們必須設(shè)計(jì)一種機(jī)制不論什么情況下,資源都能及時(shí)正確的清理。這就是finally。

public void readFile(String file) {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(
                    new FileInputStream(file)));
            // do some other work
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

例子非常的簡(jiǎn)單,是一個(gè)讀取文件的例子。這樣的例子在JDBC操作中也非常的常見(jiàn)。(所以,我覺(jué)得對(duì)于資源的及時(shí)正確清理是一個(gè)程序員的基本素質(zhì)之一。)

Try...finally結(jié)構(gòu)也是保證資源正確關(guān)閉的一個(gè)手段。如果你不清楚代碼執(zhí)行過(guò)程中會(huì)發(fā)生什么異常情況會(huì)導(dǎo)致資源不能得到清理,那么你就用try對(duì)這段"可疑"代碼進(jìn)行包裝,然后在finally中進(jìn)行資源的清理。舉一個(gè)例子:

public void readFile() {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(
                    new FileInputStream("file")));
            // do some other work
         
            //close reader
            reader.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } 
    }

我們注意一下這個(gè)方法和上一個(gè)方法的區(qū)別,下一個(gè)人可能習(xí)慣更好一點(diǎn),及早的關(guān)閉reader。但是往往事與愿違,因?yàn)樵趓eader.close()以前異常隨時(shí)可能發(fā)生,這樣的代碼結(jié)構(gòu)不能預(yù)防任何異常的出現(xiàn)。因?yàn)槌绦驎?huì)在異常出現(xiàn)的地方跳出,后面的代碼不能執(zhí)行(這在上面應(yīng)經(jīng)用實(shí)例證明過(guò))。這時(shí)我們就可以用try...finally來(lái)改造:

public void readFile() {
        BufferedReader reader = null;
        try {
            try {
                reader = new BufferedReader(new InputStreamReader(
                        new FileInputStream("file")));
                // do some other work
 
                // close reader
            } finally {
                reader.close();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

及早的關(guān)閉資源是一種良好的行為,因?yàn)闀r(shí)間越長(zhǎng)你忘記關(guān)閉的可能性越大。這樣在配合上try...finally就保證萬(wàn)無(wú)一失了(不要嫌麻煩,java就是這么中規(guī)中矩)。

再說(shuō)一種情況,假如我想在構(gòu)造方法中打開(kāi)一個(gè)文件或者創(chuàng)建一個(gè)JDBC連接,因?yàn)槲覀円谄渌姆椒ㄖ惺褂眠@個(gè)資源,所以不能在構(gòu)造方法中及早的將這個(gè)資源關(guān)閉。那我們是不是就沒(méi)轍了呢?答案是否定的??匆幌孪旅娴睦樱?/p>

public class ResourceInConstructor {
    BufferedReader reader = null;
    public ResourceInConstructor() {
        try {
            reader = new BufferedReader(new InputStreamReader(new FileInputStream("")));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
     
    public void readFile() {
        try {
            while(reader.readLine()!=null) {
                //do some work
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
     
    public void dispose() {
        try {
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

這一部分講的多了一點(diǎn),但是異常確實(shí)是看起來(lái)容易用起來(lái)難的東西呀,java中還是有好多的東西需要深挖的。

四. 異常的誤用

對(duì)于異常的誤用著實(shí)很常見(jiàn),上一部分中已經(jīng)列舉了幾個(gè),大家仔細(xì)的看一下。下面再說(shuō)兩個(gè)其他的。

例1. 用一個(gè)Exception來(lái)捕捉所有的異常,頗有"一夫當(dāng)關(guān)萬(wàn)夫莫開(kāi)"的氣魄。不過(guò)這也是最傻的行為。

public void readFile(String file) {
        BufferedReader reader = null;
        Connection conn = null;
        try {
            reader = new BufferedReader(new InputStreamReader(
                    new FileInputStream(file)));
            // do some other work
             
            conn = DriverManager.getConnection("");
            //...
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                reader.close();
                conn.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

從異常角度來(lái)說(shuō)這樣嚴(yán)格的程序確實(shí)是萬(wàn)無(wú)一失,所有的異常都能捕獲。但是站在編程人員的角度,萬(wàn)一這個(gè)程序出錯(cuò)了我們?cè)撊绾畏直媸堑降资悄且鸬哪?,IO還是JDBC...所以,這種寫(xiě)法很值得當(dāng)做一個(gè)反例。大家不要以為這種做法很幼稚,傻子才會(huì)做。我在公司實(shí)習(xí)時(shí)確實(shí)看見(jiàn)了類似的情況:只不過(guò)是人家沒(méi)有用Exception而是用了Throwable。

例2. 這里就不舉例子了,上面的程序都是反例。異常是程序處理意外情況的機(jī)制,當(dāng)程序發(fā)生意外時(shí),我們需要盡可能多的得到意外的信息,包括發(fā)生的位置,描述,原因等等。

這些都是我們解決問(wèn)題的線索。但是上面的例子都只是簡(jiǎn)單的printStackTrace()。如果我們自己寫(xiě)代碼,就要盡可能多的對(duì)這個(gè)異常進(jìn)行描述。比如說(shuō)為什么會(huì)出現(xiàn)這個(gè)異常,什么情況下會(huì)發(fā)生這個(gè)異常。如果傳入方法的參數(shù)不正確,告知什么樣的參數(shù)是合法的參數(shù),或者給出一個(gè)sample。

例3. 將try block寫(xiě)的簡(jiǎn)短,不要所有的東西都扔在這里,我們盡可能的分析出到底哪幾行程序可能出現(xiàn)異常,只是對(duì)可能出現(xiàn)異常的代碼進(jìn)行try。盡量為每一個(gè)異常寫(xiě)一個(gè)try...catch,避免異常丟失。

以上就是你必須了解的java中的異常的詳細(xì)內(nèi)容,更多請(qǐng)關(guān)注億速云其它相關(guān)文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI