Java基礎——異常及處理方法

什麼是異常?讓我們先運行一段代碼來理解

class Demo
{
    int chuFa(int x,int y)//這是一個除法運算
    {
        return x/y;
    }
}
public class ExceptionDemo {
    public static void main(String[] args) 
    {
        Demo d = new Demo();
        int a = d.chuFa(4,1);//這裏傳入的除數是1
        System.out.println(a);
        System.out.println("over");
    }
}

運行結果如下:
代碼運行結果
而當我把主函數中的除數換成0的話,我們知道在數學中這是沒有意義的運算。在Java運行時也會運行失敗,提示如下:
出現異常情況
結果表明,在程序運行的過程中出現了不正常的情況,
是什麼情況不正常呢?提示中給出,ArithmeticException:/ by zero 算術異常:被0除了

我們把這樣在程序中出現的不正常的情況稱之爲異常。

異常:就是程序在運行時出現的不正常情況。
異常由來:問題現實生活中一個具體的事物,也可以通過Java類的形式進行描述,並封裝成對象。其實就是Java對不正常情況進行描述後的對象體現。(簡單說就是把問題封裝成了對象)(這裏又體現了Java語言的封裝性和麪向對象的特徵)

對於問題的劃分,有兩種,一種是嚴重的問題,一種是非嚴重的問題
對應於嚴重的,Java通過Error類進行描述

對於非嚴重的,Java通過Exception類進行描述
對於Exception,一般都會編寫針對性的代碼進行處理

無論Error還是Exception都具有一些共性的內容。
比如:不正常情況的信息,引發原因等。
所以,Error和Exception都有一個父類叫做Throwable
異常最常見的處理方法如下:

try
{
    需要被檢測的代碼
}
catch (異常類 變量)
{
    異常的處理方式
}
finally
{
    一定會執行的語句
}

try catch方法處理異常的流程
當出現異常情況後,會創建一個異常對象,這個異常情況被try檢測到以後就會拋給catch,catch會做出對應的解決方式,處理完異常以後,之後的代碼還可以運行到。把之前的代碼加上異常處理機制後就是:

public class ExceptionDemo {
    public static void main(String[] args) 
    {
        Demo d = new Demo();
        try
        {
            int a = d.chuFa(4,0);//這裏傳入的除數是1
            System.out.println(a);
        }
        catch (Exception e)
        {
            System.out.println("除零啦");
        }

        System.out.println("over");
    }
}

運行結果就變成了:異常處理過後的運行結果
從結果中可以看出,異常處理完之後,會繼續執行下邊的語句

對於異常常見的幾種獲取信息的方法:
String getMessage();//引發錯誤的原因
String toString();//異常類+引發錯誤的原因
void printStackTrace();//異常類+引發錯誤的原因+追蹤錯誤的位置信息

聲明異常用throws關鍵字

由於在調用方法的時候會有異常發生,導致程序停止,所以在定義方法的時候要用throws關鍵字聲明一下會有異常情況,意思就是告訴調用該方法的人,這個方法可能會發生異常,你需要處理

作用:提高安全性,讓調用和作出處理,不處理則編譯失敗。

對於調用了已聲明瞭異常的方法處理方式
一旦調用了已經聲明異常的方法,就必須對其作出處理,否則會編譯失敗
處理方式有兩種:
throws :拋出,由拋出函數的外層函數進行處理,如果處理不了,再次聲明拋出可以繼續往外拋,那麼,拋到哪裏是個頭呢?
jvm虛擬機,虛擬機接收到拋出的異常之後,會使用默認的方法,讓程序停掉,
try:


多異常的處理方式

  1. 聲明異常時,建議聲明更爲具體的異常,這樣處理的可以更具體,
  2. 對方聲明幾個異常,就 對應有幾個catch塊。不要定義多餘的catc塊。
    如果多個catch塊中的異常出現繼承關係,父類異常catch塊放在最下邊,如果父類異常catch塊放在了最上邊,那麼虛擬機認爲後邊的全是廢話,編譯失敗。
    舉例如下:
class Demo
{
    int chuFa(int x,int y) throws ArithmeticException,ArrayIndexOutOfBoundsException //在這個方法裏聲明瞭兩個異常
    {
        int[] arr = new int[x];
        System.out.println(arr[4]);//如果數組角標越界的話會觸發角標越界異常
        return x/y;
    }
}
public class ExceptionDemo2 {
    public static void main(String[] args) 
    {


        Demo d = new Demo();
        try{
            int a = d.chuFa(5,1);//這裏傳入參數容易引發異常
            System.out.println(a);
        }


        catch(ArithmeticException e)//觸發ArithmeticException異常的時候執行的代碼塊
        {
            System.out.println("除零了");
        }
        catch(ArrayIndexOutOfBoundsException e)//觸發ArrayIndexOutOfBoundsException異常的時候執行的代碼塊
        {
            System.out.println("角標越界啦");
        }
        catch(Exception e)//這是父類異常的代碼塊
        {
            System.out.println("hahaha:"+e.toString());
        }
        System.out.println("over");
    }
}

這個時候沒有發生任何異常,運行會正常結束:
正常運行結果
當把chuFa方法中的參數改爲4,1時(創建一個角標越界的異常)結果如下:
角標越界異常
當把參數改爲5,0時,(創建一個運算異常) 結果如下:
運算除零異常
我們可以看到,放在最下邊的catch塊,雖然是每個異常的父類,但並沒有執行,

因爲代碼是從上到下執行的,找到了符合條件的catch塊,就不會去找其他的catch塊,這樣寫父類的異常catch塊是爲了避免其他異常蹦出來中斷程序,可以編譯通過,也可以運行但是不建議這麼寫,因爲在運行過程中出了啥異常都會被父類異常處理掉,如果出現了別的異常,程序員需要知道;

建議:在進行異常處理時,catch中一定要有具體的定義方式,不要簡單的定義一句打印異常信息,也不要簡單的書寫一條輸出語句


自定義異常
在後期做開發的時候,描述具體的事物有具體的要求,也就是說會遇到不同的異常,而這些是Java沒有進行過描述並封裝對象的

所以,對於這些特有的問題,我們可以按照Java的封裝思想,將特有的問題進行自定義的異常封裝。

怎麼樣進行異常的封裝呢?我們用代碼來體現:

//需求:定義一個除法運算,除了不能除零外,還要定義不能除以負數

class FuShuException extends Exception//定義一個異常類(對象),需要繼承Exception
{
    private int value;//想把出錯誤的數字也獲取到,在這裏創建一個value

    FuShuException(String msg,int b)//因爲自定義異常還沒有定義自己的異常信息,所以在初始化的時候就要傳一個描述性的文字,作爲他的異常信息描述語句
    {

        super(msg); 
        //Exception類中已經定義了自定義異常信息的方法,這裏把參數傳給父類Exception就行了
                                //下邊調用toString方法的時候會自動調用getMessage方法,將描述語句打印出來
        this.value = b;

    }
    public int getValue()
    {
        return value;
    }
}
class Demo
{
    int chuFa(int x,int y) throws FuShuException,ArithmeticException
    {
        if (y<0)//除數小於0,創建異常對象並拋出
        {
            throw new FuShuException("除數爲負數了 / by fushu",y);
        }
        return x/y;
    }
}
public class ExceptionDemo3 {
    public static void main(String[] args) 
    {
        Demo d = new Demo();
        try//對自定義的異常進行處理
        {
            int a = d.chuFa(4,-1);
            System.out.println(a);
        }
        catch (FuShuException e)
        {
            System.out.println(e.toString()+"---錯誤的數字是"+e.getValue());
        }
        catch(ArithmeticException e)
        {
            System.out.println(e.toString());
        }

    }
}

運行結果爲:
自定義異常運行結果
我們可以看到,自定義異常,必須是自定義類繼承Exception

繼承Exception 的原因:
異常體系中有一個特點,因爲異常類和對象都被拋出
他們都具備可拋性。這個可拋性是Throwable這個體系中的獨有特點

注意的是,在自定義異常的時候,沒有必要所有的信息都自己去做,
比如異常信息的操作,在父類中就已經做完了,父類中有這樣的方法:
Throwable中的構造方法摘要
而在Exception類中,也有相同的方法:
這裏寫圖片描述
所以我們這裏只要把自定義的信息用super語句傳給父類。
就是以上代碼中的 “super(msg);”
就可以直接通過getMessage方法獲取自定義的異常信息。


throw和throws的區別
throws使用在函數上,throw使用在函數內
throws後邊跟的是異常類,可以跟多個,用逗號隔開
throw後跟的是異常對象

運行時異常
Exception中有一個特殊的子類異常RuntimeException

如果在函數中拋出該異常,函數上可以不用聲明,編譯一樣通過。
如果在函數上聲明瞭該異常,調用者可以不用進行處理,編譯一樣通過。

之所以不用在函數聲明,是因爲不需要讓調用者處理
當該異常發生,希望程序停止,因爲在運行時,出現了無法繼續運算的情況,希望程序停止後,對代碼進行修正。

子類例如:數組角標越界異常(角標越界都找不到元素,沒有辦法繼續運算下去了),空指針異常(對象沒有指向的示例化對象,也無法進行下一步的運算)

這些情況下,我們希望程序停下來,不希望catch處理之後繼續再進行運算,
因爲再運算也沒有什麼意義了。

用一段小代碼來具體體現一下RunTimeException的特點:

//運行時異常:

//需求:定義一個除法運算,除了不能除零外,還要定義不能除以負數
        //一旦發生除零,除負數的情況,應該讓程序停下來,因爲繼續算也沒什麼 意義了

class FuShuException extends RuntimeException//該種異常我們不會處理,發生該異常的時候希望程序停下來,所以這裏要繼承RunTimeException
{

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

}
class Demo
{
    int chuFa(int x,int y)//因爲裏邊拋出的是RunTimeException,所以在這裏可以不用聲明,在主函數中也不用處理
    {
        if (y<0)
        {
            throw new FuShuException("除數爲負數了 / by fushu");
        }
        if (y==0)
        {
            throw new ArithmeticException("除數爲0,沒意義");
        }
        return x/y;
    }
}
public class ExceptionDemo4 {
    public static void main(String[] args) 
    {
        Demo d = new Demo();

            int a = d.chuFa(4,0);
            System.out.println(a);

    }
}

運行結果爲下圖:
運行時異常代碼運行結果

在自定義異常的時候:如果該異常的發生無法再繼續進行運算,就讓自定義異常繼承RunTimeException。

對於異常分爲兩種:
1,編譯時被檢測的異常。
2,編譯時不被檢測的異常(運行時異常,RunTimeException以及其子類)

所以在分析問題定義異常的時候,先要分析該異常能不能被處理,
|—–如果不能處理,需要修正代碼的時候,就繼承RunTimeException,
|—–如果該異常可以被try catch處理,就繼承Exception。


一個小練習:老師帶電腦上課的過程中異常處理:

//異常練習
/*
需求:畢老師講課,要用電腦講課,

而會時常有異常,比如:電腦藍屏了,:處理方式,重啓,繼續上課

                      電腦冒煙了。:處理方式,又有了一個新的異常:
                            課時無法完成,處理方式:換老師或者放假


思想:封裝,將各種異常封裝成對象,關鍵字提取;

*/

class LanPingException extends Exception//藍屏異常
{
    LanPingException(String msg)
    {
        super(msg);
    }
}
class MaoYanException extends Exception//冒煙異常
{
    MaoYanException(String msg)
    {
        super(msg);
    }
}
class NoPlanException extends Exception//課時無法繼續異常
{
    NoPlanException(String msg)
    {
        super(msg);
    }
}
class Teacher
{
    private String name;
    Teacher(String name)
    {
        this.name = name;
    }
    public void jiangKe() throws NoPlanException//老師講課過程中會拋出課時無法繼續異常,所以在這裏標識一下

    {
        //老師來講課,帶上了自己的電腦,開啓電腦之後開始講課
        Computer cmpt = new Computer();
        try
        {
            cmpt.run();//而電腦會出現異常,所以在這裏調用run方法的時候要對異常進行處理
        }
        catch (LanPingException e)
        {
            cmpt.restart();
        }
        catch (MaoYanException e)
        {
            //發生冒煙異常的時候,處理方法應該是先讓學生們做練習,然後給調用講課方法的人拋一個課時無法完成異常
            //交給上一層領導處理
            test();
            throw new NoPlanException("課時無法繼續");
        }
        System.out.println("開始講課");

    }
    public void test()
    {
        System.out.println("做練習");
    }
}
class Computer
{
    private int state = 3;
    public void run() throws MaoYanException,LanPingException
    {
        //電腦的諸多自定義異常都要在這裏產生異常對象
        if(state==2)
        {
            throw new LanPingException("藍屏了");
        }
        if (state==3)
        {
            throw new MaoYanException("冒煙了");
        }
        System.out.println("電腦運行");
    }
    public void restart()
    {
        System.out.println("電腦重啓");
    }
}
public class ExceptionTest{
    public static void main(String[] args) 
    {
        Teacher t = new Teacher("畢老師");
        try
        {
            t.jiangKe();//在調用講課方法的時候,接收到課時無法完成異常,將在這裏進行處理
        }
        catch (NoPlanException e)
        {
            System.out.println("換老師或者放假-----"+e.getMessage());
        }

    }
}

運行結果如下:
當代碼中state爲1的時候(電腦沒有發生任何異常的時候)結果爲:
無異常運行結果

當代碼中state爲2的時候(電腦發生藍屏異常的時候)結果爲:
藍屏異常運行結果

當代碼中state爲3的時候(電腦發生冒煙異常的時候)結果爲:
冒煙異常運行結果


異常——finally

finally代碼塊,定義一定會執行的代碼,通常用於關閉資源

在數據庫操作中最爲常見的例子:

public void method()
{
    連接數據庫;

    數據操作//throw new SQLException如果拋出異常,下面的代碼就不會運行了,也就是說資源沒有被關閉,

    關閉數據庫//而這一步是必須要執行的
}

該代碼中如果異常出現,則不會關閉資源,這不是我們想要的。
解決這樣的問題可以這麼做:

public void method() throws 沒有操作成功異常
{
    try
    {
        連接數據庫;
        數據操作;//throw new 數據操作異常;
    }
    catch(數據操作異常 e)
    {
        會對數據庫異常進行處理;
        throw new 沒有操作成功異常;
    }
    finally
    {
        關閉數據庫;
    }
}

在Java中,數據存儲操作是調用數據庫的方法完成的,
我要存數數據,把數據給你即可,至於你是怎麼存的,我沒必要知道,
但是我必須知道的是:數據有沒有存儲成功,

就像在以上的代碼中,數據庫操作出現異常,應該在數據庫內部直接處理,
而不是拋出來異常,因爲拋出來我也看不懂,所以在你內部直接處理掉就可以了。
但是你要告訴我,有沒有存儲成功,也就是說這裏可以拋出來一個“沒有操作成功異常”這個我可以處理。

而不管有沒有存儲成功,資源是必須要關閉的,所以我們在這裏把關閉資源的動作放在finally語句中。

異常處理語句的其他格式:

//第一種格式:
try
{
}
catch()
{
}
//第二種格式:
try
{
}
caatch()
{
}
finally
{
}
//第三種格式
try
{
}
finally
{
}

記住一點:catch是用來處理異常的,如果沒有catch代表該異常沒有被處理過,如果該異常時檢測時異常,那麼必須申明。

異常在子父類覆蓋中的體現

  1. 子類在覆蓋父類時,如果父類的方法拋出異常,那麼子類的覆蓋方法只能拋出父類的異常,或者父類異常的子類,
  2. 如果父類方法拋出多個異常,那麼子類在覆蓋該方法時,只能拋出父類異常的子集。
  3. 如果父類或者接口的方法中沒有異常拋出,那麼子類在覆蓋方法時,也不可以拋出異常。如果子類方法拋出了異常嗎,就必須要進行try處理,絕對不能拋。

  4. 用一個代碼練習來說明這段文字:
//異常之間的關係是:
//   |--Exception
//      |--AException
//          |--BException
//      |--CException
class AException extends Exception
{

}
class BException extends AException
{

}
class CException extends Exception
{

}


class Fu 
{
    public void show() throws AException//父類方法拋出一個AException
    {
    }
}
class Zi extends Fu//子類繼承父類
{
    public void show()throws BException//在這裏拋出一個AException(也可以拋出BException,但是不能拋出CException)
                                        //如果這裏真的會冒出一個C異常的話,只能try,絕對不能拋
    {

    }
}
class Test1
{
    public void function(Fu f)
    {
        try
        {
            f.show();
        }
        catch (AException e)
        {

        }

    }
}
public class Test {
    public static void main(String[] args)
    {

        Test1 t1 = new Test1();
        t1.function(new Zi());
    }
}

通過一串小代碼來更加熟練的運用一下異常的處理機制:

//長方形圓形的面積計算程序


/*
首先思考:長方形,圓形都是圖形,都有獲取面積的這樣一個功能,
但是長和寬是長方形特有的,半徑是圓特有的屬性
可以將獲取面積的方法封裝在一個接口中
*/
interface Shape
{
    void getArea();
}
//長方形的計算過程:
class ChangFangXing implements Shape
{
    private int chang;
    private int kuang;
    ChangFangXing(int chang,int kuang)
    {
        this.chang = chang;
        this.kuang = kuang;
    }
    public void getArea()
    {
        //如果給出的值小於0的話,必須停掉程序,不可以再往下運行了。
        if (chang<=0 || kuang<=0)
        {
            throw new NoValueException("長或寬的值小於0!!!");
        }
        System.out.println(chang*kuang);
    }

}
//圓的面積計算過程:
class Yuan implements Shape
{
    private int radiu;
    //PI的值是不變的,所以在這裏把他定義成常量
    public static final double PI = 3.14;
    Yuan(int radiu)
    {
        this.radiu = radiu;
    }
    public void getArea()
    {
        //同樣的,如果他的半徑小於0,程序停掉
        if (radiu<=0)
        {
            throw new NoRadiuException("圓的半徑值出錯!!!");
        }
        System.out.println(radiu*radiu*PI);
    }
}
class NoValueException extends RuntimeException
{
    NoValueException(String msg)
    {
        super(msg);
    }
}
class NoRadiuException extends RuntimeException
{
    NoRadiuException(String msg)
    {
        super(msg);
    }
}
public class ExceptionTest2 {
    public static void main(String[] args) 
    {
        //創建一個長方形對象,求面積
        ChangFangXing cfx = new ChangFangXing(3,4);
        cfx.getArea();
        //創建一個圓的對象,並求面積
        Yuan y = new Yuan(2);
        y.getArea();
    }
}

運算結果爲:
面積運算程序結果

異常知識總結:

  • 異常是什麼?
    • 是對問題的描述,並將問題對象封裝成對象
  • 異常體系:
    • Throwable
      • Error
      • Exception
        • RuntimeException
  • 異常體系的特點:異常體系中所有類以及建立的對象都具有可拋性。也就是說可以被throw和throws關鍵字操作,只有異常體系具備這個特點。
  • throw和throws的用法:
    • throw定義在函數內,用於拋出異常對象
    • throws定義在函數上,用於拋出異常類,可以拋出多個,用逗號隔開。
  • 當函數內容有throw拋出異常對象,並未進行try處理,必須要在函數上聲明,否則編譯失敗
  • 注意:RunTimeException除外,也就是說,函數中如果拋出RunTimeException函數上可以不用聲明
  • 如果函數上聲明瞭異常,調用者需要進行處理。處理方法可以是try和throws
  • 異常有兩種:
    • 編譯時被檢測異常
      • 該異常在編譯時,如果沒有進行過處理(沒有拋也沒有try),編譯失敗
      • 該異常被標識,代表着可以被處理
    • 運行時異常(編譯時不檢測)
      • 在編譯時,不需要處理,編譯器不檢查。
      • 該異常發射給你時,建議不處理,讓程序停止,需要對代碼進行修正
  • 異常處理語句:
    try
    {
    需要被檢測的代碼;
    }
    catch()
    {
    處理異常的代碼;
    }
    finally
    {
    一定會執行的代碼;
    }
    有三個結合方式:
    1.try catch
    2.try finally
    3.try catch finally
  • 注意:
    • finally中通常是關閉資源的代碼,因爲資源必須釋放
    • finally只有一種情況不會執行,當執行到System.exit(0);finally不會執行,因爲jvm結束運行了
  • 自定義異常:
    • 定義類繼承Exception或者RuntimeException
    • 1.爲了讓該自定義類具備可拋性。
    • 2.讓該類具備操作異常的共性方法
    • 3.當要定義自定義異常信息時,可以使用父類已經定義好的功能。將異常信息傳遞給父類的構造函數
class MyException extends Exception
{
    MyException(String message)
    {
        super(message);
    }
}
  • 自定義異常:按照Java面向對象的思想,將程序中出現的特有問題進行封裝
  • 異常的好處:
    • 將問題進行封裝。
    • 將正常流程代碼和問題處理代碼相分離,方便於閱讀。
  • 異常的處理原則:
    • 1,處理方式有兩種:try 或者throws
    • 2,調用到拋出異常的功能時,拋出幾個,就處理幾個。
      • 一個try對應多個catch
    • 3,多個catch,父類的catch放到最下邊
    • 4,catch內,不需要定義針對性的處理方式,不要簡單的定義printstrackTrace,也不要不寫
      • 當捕獲到的異常,本功能處理不了時,可以繼續在catch中拋出。
try
{
    throw new AException();
}
catch(AException e)
{
    throw e
}

如果該異常處理不了,但並不屬於該功能出現的異常。
可以將異常轉換後,再拋出和該功能相關的異常。
或者異常可以處理,但需要將異常產生的和本功能相關的問題提供出來
讓調用者知道,並處理,也可以將捕獲異常處理後,轉換新的異常拋出

try
{
    throw new AException();
}
catch(AException e )
{
    //對AException處理。
    throw new BException();
}

比如:匯款的例子。
我去給張三匯款,但是出了異常沒有匯成功,匯款機需要先將我匯的錢再存進我的卡里,然後告訴我,沒有匯成功,我再去處理這個沒有匯成功的異常(換個銀行或者換臺機子)

  • 異常的注意事項:
    • 在子父類覆蓋時:
    • 1,子類拋出的異常必須是父類的異常或者異常的子類,或者子集
    • 2,如果父類或者接口沒有異常拋出時,子類覆蓋出現異常,只能try不能throws
      參閱例子:
      老師用電腦上課
      圖形面積
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章