1. 程式人生 > >Java學習12:異常

Java學習12:異常

概念

程式在執行時出現不正常的情況。

異常由來

問題也是現實生活中的一個具體的事物,也可以通過java類的形式進行描述,並且封裝成物件,其實就是java對不正常情況進行描述後的物件體現。
對於問題的劃分有兩種:嚴重的,java通過Error類進行描述,一般不編寫針對性的程式碼對其進行處理;不嚴重的,通過Exception類進行描述,可以使用針對性的處理方式進行處理。
Throwable
|–Error
|–Exception

異常處理

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

/*
    div 0!
    / by zero
    java.lang.ArithmeticException: / by zero
    java.lang.ArithmeticException: / by zero
    at Function.div(Demo.java:178)
    at Demo.main(Demo.java:187)
    over
*/
class Function
{
    double div(int a, int b)
    {
        return a / b;//(1) new AritchmeticException()
    }
}
class Demo
{
    public
static void main(String[] args) { Function f = new Function(); try { double x = f.div(1, 0);//(2) (1)傳過來的new AritchmeticException() System.out.println("x = " + x); } catch(Exception e)//(3) Exception e = new AritchmeticException(); { System.out.println("div 0!"
); System.out.println(e.getMessage()); System.out.println(e.toString());// 異常名稱:異常資訊 e.printStackTrace();// 異常名稱,異常資訊,異常出現的位置 其實JVM預設的異常處理機制,就是在呼叫printStackTrace方法,列印異常的堆疊的跟蹤資訊。 } System.out.println("over"); } }

對捕獲到的異常物件進行常見方法操作

String getMessage() 返回此throwable的詳細訊息字串
String toString() 返回此throwable的簡短表現形式
void printStackTrace() 將此throwable及其追蹤輸出至標準錯誤流

throws

在函式上通過throws的關鍵字聲明瞭該功能有可能會出現問題。便於提高安全性,讓呼叫者進行處理(捕獲或丟擲),不處理編譯失敗。

class Function
{
    double div(int a, int b) throws Exception
    {
        return a / b;
    }
}
class Demo
{
    public static void main(String[] args) //throws Exception
    {
        Function f = new Function();
        try {//不捕獲編譯會報錯(或者在主函式後面丟擲異常)  Error:(187, 29) java: 未報告的異常錯誤java.lang.Exception; 必須對其進行捕獲或宣告以便丟擲
            double x = f.div(1, 0);
            System.out.println("x = " + x);
        }
        catch(Exception e)
        {
            System.out.println(e.toString());

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

對多異常的處理

  1. 宣告異常時,建議宣告為更具體的異常,這樣處理的可以更具體。
  2. 對方宣告幾個異常, 就對應有幾個catch塊。如果多個catch中的異常出現繼承關係,父類異常catch塊放在最下面。
  3. 在進行catch處理時,catch中一定要定義具體處理方式,不要簡單定義一句e.printStackTrace(),也不要簡單的書寫一條輸出語句。
class Function
{
    double div(int a, int b) throws ArithmeticException, ArrayIndexOutOfBoundsException
    {
        int[] x = new int[a];
        System.out.println(x[4]);
        return a / b;
    }
}
class Demo
{
    public static void main(String[] args) //throws Exception  拋給虛擬機器
    {
        Function f = new Function();
        try {
            double x = f.div(5, 0);
            System.out.println("x = " + x);
        }
        catch(ArithmeticException e)//java.lang.ArithmeticException: / by zero
        {
            System.out.println(e.toString());
            System.out.println("div 0!");
        }
        catch(ArrayIndexOutOfBoundsException e)//java.lang.ArrayIndexOutOfBoundsException: 4
        {
            System.out.println(e.toString());
            System.out.println("陣列越界!");
        }
        System.out.println("over");
    }
}

自定義異常

因為專案中會出現特有的問題,這些問題並沒有被java所描述並封裝物件,所以這些特有的問題可以按照java的對問題封裝的思想,將特有的問題進行自定義的異常封裝。
需求:本程式中,對於除數為負數,也視為是錯誤的,是無法進行運算的,那麼僅需要對此問題進行自定義的描述。

/*
    FuShuException
    div FuShu!
    over
*/
class FuShuException extends Exception//自定義異常
{}
class Function
{
    double div(int a, int b) throws ArithmeticException, FuShuException
    {
        if(b < 0)
            throw new FuShuException();
        return a / b;
    }
}
class Demo
{
    public static void main(String[] args)
    {
        try
        {
            double result = new Function().div(1, -1);
            System.out.println("result = " + result);
        }
        catch(ArithmeticException e)
        {
            System.out.println(e.toString());
            System.out.println("div 0!");
        }
        catch(FuShuException e)
        {
            System.out.println(e.toString());
            System.out.println("div FuShu!");
        }
        System.out.println("over");
    }
}

上述程式中的列印結果中只有異常的名稱,卻沒有異常的資訊,因為自定義的異常中未定義資訊。如何定義異常資訊?
因為父類中已經把異常資訊的操作都完成了,所以子類只要在構造時,將異常資訊通過super語句傳遞給父類,那麼就可以直接通過getMessage方法獲取自定義的異常資訊(toString呼叫getMessage方法)。下面程式中還包含了傳遞特有資訊。

/*
    FuShuException: / by fushu
    div FuShu!
    illegal negative value is : -1
    over
 */
class FuShuException extends Exception//自定義異常
{
      private int value;
      FuShuException(String msg, int value)
      {
           super(msg);
           this.value = value;
      }
      int getValue()
      {
          return value;
      }
//    private String msg;
//    FuShuException(String msg)
//    {
//        this.msg = msg;
//    }
//    public String getMessage()
//    {
//        return msg;
//    }
}
class Function
{
    double div(int a, int b) throws ArithmeticException, FuShuException
    {
        if(b < 0)
            throw new FuShuException("/ by fushu", b);
        return a / b;
    }
}
class Demo
{
    public static void main(String[] args)
    {
        try
        {
            double result = new Function().div(1, -1);
            System.out.println("result = " + result);
        }
        catch(ArithmeticException e)
        {
            System.out.println(e.toString());
            System.out.println("div 0!");
        }
        catch(FuShuException e)
        {
            System.out.println(e.toString());
            System.out.println("div FuShu!");
            System.out.println("illegal negative value is : " + e.getValue());
        }
        System.out.println("over");
    }
}

注意:自定義類必須是繼承Exception,繼承Exception的原因:
異常體系有一個特點,因為異常類和異常物件都被丟擲,他們都具有可拋性,這個可拋性Throwable這個體系中獨有特點,只有這個體系中的類和物件才可以被throws和throw操作。

throws和throw的區別

throws使用在函式上,throw使用在函式內。
throws後面跟的異常類,可以跟多個,用逗號隔開。throw後面跟的是異常物件。

RuntimeException

Exception中有一個特殊的子類異常RuntimeException(執行時異常)。
如果在函式內中丟擲該異常,函式上可以不用宣告,編譯一樣通過(其他異常編譯不通過);
class Function
{
    double div(int a, int b)
    {
        if(b == 0)
            //throw new Exception("div zero!"); 就會報錯"未報告的異常錯誤java.lang.Exception; 必須對其進行捕獲或宣告以便丟擲"
            throw new ArithmeticException("div zero!");
        return a / b;
    }
}
class Demo
{
    public static void main(String[] args)
    {
        Function f = new Function();
        f.div(1, 0);
        System.out.println("over!");
    }
}

: 如果在函式上聲明瞭該異常,呼叫者可以不用進行處理,編譯一樣通過。

class Function
{
    double div(int a, int b) throws ArithmeticException//如果是throws Exception,則會報錯“未報告的異常錯誤java.lang.Exception; 必須對其進行捕獲或宣告以便丟擲”
    {
        return a / b;
    }
}
class Demo
{
    public static void main(String[] args)
    {
        Function f = new Function();
        f.div(1, 0);
        System.out.println("over!");
    }
}

之所以不用在函式宣告,是因為不需要讓呼叫者處理,當該異常發生,希望程式停止(我的理解就是這個異常太嚴重,已經無法處理),因為在執行時出現了無法繼續運算的情況,希望停止程式後,對程式碼進行修正。

所以,自定義異常時,如果該異常的發生,無法再繼續進行運算,就讓自定義異常繼承RuntimeException。

//Exception in thread "main" FuShuException: div Fushu!
class FuShuException extends RuntimeException
{
    FuShuException(String msg)
    {
        super(msg);
    }
}
class Function
{
    double div(int a, int b)//throws FuShuException
    {
        if(b < 0)
            //FuShuException繼承了RuntimeException,所以不用宣告異常,主函式不需要處理
            throw new FuShuException("div Fushu!");
        return a / b;
    }
}
class Demo
{
    public static void main(String[] args)
    {
        new Function().div(1, -2);
    }
}

異常分為兩種

  1. 編譯時被檢測的異常;
  2. 編譯時不被檢測的異常(執行時異常,RuntimeException及其子類)

練習

class BlueScreenException extends Exception
{
    BlueScreenException(String msg)
    {
        super(msg);
    }
}
class SmokeException extends Exception
{
    SmokeException(String msg)
    {
        super(msg);
    }
}
class NoPlanException extends Exception
{
    NoPlanException(String msg)
    {
            super(msg);
    }
}
class Computer
{
    private int status = 2;
    public void run() throws BlueScreenException, SmokeException
    {
        if(status == 2)
            throw new BlueScreenException("電腦藍屏!");
        else if(status == 3)
            throw new SmokeException("電腦冒煙!");
        System.out.println("電腦執行!");
    }
    public void reset()
    {
        System.out.println("電腦重啟!");
    }
}
class Teacher
{
    public void test()
    {
        System.out.println("練習!");
    }
    public void prelect() throws NoPlanException
    {
        Computer comp =  new Computer();
        try {
            comp.run();
        }
        catch(BlueScreenException e)
        {
            System.out.println(e.toString());
            comp.reset();
        }
        catch(SmokeException e)
        {
            test();
            throw new NoPlanException("課程無法繼續," + e.getMessage());
        }
        System.out.println("講課!");
    }
}
class Demo
{
    public static void main(String[] args)
    {
        Teacher t = new Teacher();
        try {
            t.prelect();
        }
        catch (NoPlanException e)
        {
            System.out.println(e.toString());
            System.out.println("更換電腦");
        }
    }
}

finally程式碼塊

定義一定執行的程式碼,通常用於關閉資源。(在資料庫操作中將關閉資料庫放在這裡面)

三種格式

    //1
    try
    {
    }
    catch()
    {

    //2
    try
    {
    }
    catch()
    {
    }
    finally
    {
    }

    //3
    try
    {
    }
    finally
    {
    }

注意: catch是用於處理異常,如果沒有catch就代表沒有被處理過,如果該異常是檢測時異常,那麼必須宣告,見下面程式。

class Demo
{
    public void method()
    {
        //1.編譯不能通過 解決方法:(1)宣告異常throws Exception;(2)捕獲異常
        throw new Exception();

        //2.編譯能通過,捕獲異常
        try
        {
            throw new Exception();
        }
        catch (Exception e)
        {

        }

        //3.編譯不能通過,異常未被宣告或捕獲
        try
        {
            throw new Exception();
        }
        catch (Exception e)
        {
            throw e;
        }

        //4.編譯能通過,捕獲異常
        try
        {
            throw new Exception();
        }
        catch (Exception e)
        {
            try
            {
                throw e;
            }
            catch(Exception e2)
            {

            }
        }

        //5.編譯不能通過,異常未被宣告或捕獲
        try
        {
            throw new Exception();
        }
        finally
        {
            //關閉資源
        }
    }
}

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

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

異常例子

class InvalidValueException extends RuntimeException
{
    InvalidValueException(String msg)
    {
        super(msg);
    }
}
interface Shape//介面類
{
    public abstract double getArea();
}
class Rec implements Shape//長方形
{
    private double len, width;
    Rec(double len, double width)
    {
        this.len = len;
        this.width = width;
    }
    public double getArea()
    {
        if(len <= 0 || width <= 0)
            throw new InvalidValueException("輸入非法值!");
        return len * width;
    }
}
class Circle implements Shape
{
    double r;
    private static final double PI = 3.14;//因為PI是共享資料,所以加static
    Circle(double r)
    {
        this.r = r;
    }
    public double getArea()
    {
        if(r <= 0)
            throw new InvalidValueException("出現非法值");
        return r * r * PI;
    }
}
class Demo
{
    public static void main(String[] args)
    {
        Rec rec = new Rec(10, 2);
        System.out.println(rec.getArea());
        Circle circle = new Circle(-11);
        System.out.println(circle.getArea());
    }
}

總結

異常是對問題的描述,將問題進行物件的封裝。

異常體系:
Throwable
|–Error
|–Exception
異常體系特點:異常體系中所有類以及建立的物件都具有可拋性,也就是說可以被throw和throws關鍵字所操作,只有異常體系具備這個特點,

throw和throws的用法:
throw定義在函式內,用於丟擲異常物件;
throws定義在函式上,用於丟擲異常類,可以丟擲多個,用逗號隔開。

當函式內容有throw丟擲異常物件,並未進行try處理,必須要在函式上宣告,否則編譯失敗。
注意:RuntimeException除外,也就是說,函式內如果丟擲RuntimeException異常,函式上可以不用宣告。如果函式聲明瞭異常,呼叫者需要進行處理,處理方式可以是throws/try。

異常有兩種:
編譯時被檢測異常,該異常如果沒有處理,沒有throws也沒有try,編譯失敗,該異常被標識,代表可以被處理;
執行時異常(編譯時不檢測),在編譯時不需要處理,編譯器不檢查,該異常發生時,建議不處理,讓程式停止,需要對程式碼進行修正。

異常處理語句
try
{
需要被檢測的程式碼;
}
catch()
{
處理異常的程式碼;
}
finally
{
一定會執行的程式碼;
}
注意:
(1)finally中定義的通常是關閉資原始碼,因為資源必須釋放;
(2)finally只有一種情況不會執行,當catch中寫到System.exit(0)時系統退出,jvm結束,此時finally裡面的語句不執行。

自定義類異常

按照java的面向物件思想, 將程式中出現的特有問題進行封裝,定義類繼承Exception或者RuntimeException。
(1)為了讓自定義類具備可拋性;
(2)讓該類具備操作異常的共性方法。

當要定義自定義異常的資訊時,可以使用父類已經定義好的功能,異常資訊傳遞給父類的建構函式。

class MyException extends Exception
{
    MyException(String msg)
    {
        super(msg);
    }
}

異常的好處
(1)將問題進行封裝;
(2)將正常流程程式碼和問題處理程式碼相分離,方便於閱讀。

異常的處理原則
(1)處理方式:try或者throws;
(2)呼叫到丟擲異常的功能時,丟擲幾個,就處理幾個,一個try對應多個catch;
(3)多個catch,父類的catch放到最下面;
(4)catch內,需要定義針對性的處理方式,不要簡單地定義printStackTrace輸出語句,也不要不寫;
(5)當捕獲到的異常,本功能處理不了時,可以繼續在catch中丟擲;

try
{
    cicle.getArea();
}
catch(InvalidValueException e)
{
    throw e;
}

(6)如果該異常處理不了,但並不屬於該功能出現的異常, 可以將異常轉換後,再丟擲和功能相關的異常;或者異常可以處理,當需要將異常產生的和本功能相關的問題提供出去,讓呼叫者知道並處理,也可以將捕獲異常處理後轉換為新的異常;

//1
try
{
    cicle.getArea();
}
catch(InvalidValueException e)
{
    throw new BException();
}
//2
try
{
    cicle.getArea();
}
catch(InvalidValueException e)
{
    //對e進行處理,之後丟擲新的異常
    throw new BException();
}

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