java之異常詳解


一、什么是異常?

異常就是有異於常態,和正常情況不一樣,有錯誤出錯。在java中,阻止當前方法或作用域正常運行的情況,稱之為異常。

 

二、異常體系

Java把異常當作對象來處理,並定義一個基類java.lang.Throwable作為所有異常的超類。在Java API中已經定義了許多異常類,這些異常類分為兩大類,錯誤Error和異常Exception。

Throwable分成了兩個不同的分支:

Error:它表示不希望被程序捕獲或者是程序無法處理的錯誤。Error類對象由 Java 虛擬機生成並拋出,大多數錯誤與代碼編寫者所執行的操作無關。

Exception:它表示用戶程序可能捕捉的異常情況或者說是程序可以處理的異常。Exception以及他的子類,代表程序運行時發送的各種不期望發生的事件。可以被Java異常處理機制使用,是異常處理的核心。

Error和Exception的區別:Error通常是災難性的致命的錯誤,是程序無法控制和處理的,當出現這些異常時,Java虛擬機(JVM)一般會選擇終止線程;Exception通常情況下是可以被程序處理的,並且在程序中應該盡可能的去處理這些異常。

 

根據Javac對異常的處理要求,將異常類分為2類:

非檢查異常(unckecked exception):Error 和 RuntimeException 以及他們的子類。javac在編譯時,不會提示和發現這樣的異常,不要求在程序處理這些異常,對於這些異常,我們應該修正代碼,而不是去通過異常處理器處理 。常見的RuntimeException :

ArrayIndexOutOfBoundsException(數組下標越界)

NullPointerException(空指針異常)

ArithmeticException(算術異常)

MissingResourceException(丟失資源)

ClassNotFoundException(找不到類)

 

檢查異常(checked exception):除了Error 和 RuntimeException的其它異常。javac強制要求程序員為這樣的異常做預備處理工作(使用try catch finally或者throws)。在方法中要么用try-catch語句捕獲它並處理,要么用throws子句聲明拋出它,否則編譯不會通過。這樣的異常一般是由程序的運行環境導致的。因為程序可能被運行在各種未知的環境下,而程序員無法干預用戶如何使用他編寫的程序,於是程序員就應該為這樣的異常時刻准備着。如SQLException , IOException,ClassNotFoundException 等

 如果只考慮Exception,則非檢查異常(unckecked exception)等同於RuntimeException,檢查異常等同於非運行時異常。

 

三、異常處理

異常的處理機制分為拋出異常和捕獲異常

拋出異常:在當前環境下無法獲得必要的信息來解決問題,你所能做的就是從當前環境中跳出,並把問題提交給上一級環境,這就是拋出異常時所發生的事情。拋出異常后,會有幾件事隨之發生。首先,是像創建普通的java對象一樣將使用new在堆上創建一個異常對象;然后,當前的執行路徑(已經無法繼續下去了)被終止,並且從當前環境中彈出對異常對象的引用。此時,異常處理機制接管程序,並開始尋找一個恰當的地方繼續執行程序,這個恰當的地方就是異常處理程序或者異常處理器,它的任務是將程序從錯誤狀態中恢復,以使程序要么換一種方式運行,要么繼續運行下去。

 

捕獲異常:在方法拋出異常之后,運行時系統將轉為尋找合適的異常處理器(exception handler)。潛在的異常處理器是異常發生時依次存留在調用棧中的方法的集合。當異常處理器所能處理的異常類型與方法拋出的異常類型相符時,即為合適的異常處理器。運行時系統從發生異常的方法開始,依次回查調用棧中的方法,直至找到含有合適異常處理器的方法並執行。當運行時系統遍歷調用棧而未找到合適的異常處理器,則運行時系統終止。同時,意味着Java程序的終止。

 

Java異常處理涉及到五個關鍵字,分別是:try、catch、finally、throw、throws:

   try     -- 用於監聽。將要被監聽的代碼(可能拋出異常的代碼)放在try語句塊之內,當try語句塊內發生異常時,異常就被拋出。

  catch   -- 用於捕獲異常。catch用來捕獲try語句塊中發生的異常。

  finally  -- finally語句塊總是會被執行。它主要用於回收在try塊里打開的物力資源(如數據庫連接、網絡連接和磁盤文件)。只有finally塊,執行完成之后,才會回來執行try或者catch塊中的return或者throw語句,如果finally中使用了return或者throw等終止方法的語句,則就不會跳回執行,直接停止。

  throw   -- 用於拋出異常。

       throws -- 用在方法簽名中,用於聲明該方法可能拋出的異常。

 

捕獲異常:try catch / try catch finally

1、try-catch語句

try {
                  //可能產生的異常的代碼區,也成為監控區
        }catch (ExceptionType1 e) {
                  //捕獲並處理try拋出異常類型為ExceptionType1的異常
        }catch(ExceptionType2 e) {
                  //捕獲並處理try拋出異常類型為ExceptionType2的異常
     }

2、try-catch-finally

try {
       //可能產生的異常的代碼區
   }catch (ExceptionType1 e) {
       //捕獲並處理try拋出異常類型為ExceptionType1的異常
   }catch (ExceptionType2 e){
       //捕獲並處理try拋出異常類型為ExceptionType2的異常
   }finally{
       //無論是出現異常,finally塊中的代碼都將被執行
   }

3、try-catch-finally代碼塊的執行順序:

      A)try沒有捕獲異常時,try代碼塊中的語句依次被執行,跳過catch。如果存在finally則執行finally代碼塊,否則執行后續代碼。

      B)try捕獲到異常時,如果沒有與之匹配的catch子句,則該異常交給JVM處理。如果存在finally,則其中的代碼仍然被執行,但是finally之后的代碼不會被執行。

      C)try捕獲到異常時,如果存在與之匹配的catch,則跳到該catch代碼塊執行處理。如果存在finally則執行finally代碼塊,執行完finally代碼塊之后繼續執行后續代碼;否則直接執行后續代碼。另外注意,try代碼塊出現異常之后的代碼不會被執行。

 

注意點:

1、try塊中的局部變量和catch塊中的局部變量(包括異常變量),以及finally中的局部變量,他們之間不可共享使用。

2、每一個catch塊用於處理一個異常。異常匹配是按照catch塊的順序從上往下尋找的,只有第一個匹配的catch會得到執行。匹配時,不僅運行精確匹配,也支持父類匹配,因此,如果同一個try塊下的多個catch異常類型有父子關系,應該將子類異常放在前面,父類異常放在后面,這樣保證每個catch塊都有存在的意義。

3、java中,異常處理的任務就是將執行控制流從異常發生的地方轉移到能夠處理這種異常的地方去。也就是說:當一個函數的某條語句發生異常時,這條語句的后面的語句不會再執行,它失去了焦點。執行流跳轉到最近的匹配的異常處理catch代碼塊去執行,異常被處理完后,執行流會接着在“處理了這個異常的catch代碼塊”后面接着執行

 

小結:

  1、不管有木有出現異常或者try和catch中有返回值return,finally塊中代碼都會執行;

  2、finally中最好不要包含return,否則程序會提前退出,返回會覆蓋try或catch中保存的返回值。

  3.  e.printStackTrace()可以輸出異常信息。

  4.  return值為-1為拋出異常的習慣寫法。

  5.  如果方法中try,catch,finally中沒有返回語句,則會調用這三個語句塊之外的return結果。

       6.  finally 在try中的return之后 在返回主調函數之前執行。

 

接下來看兩段有意思的代碼:

代碼一:

public class TestFinally {
    public static void main(String[] args){
        int[] a = test1();
        System.out.println(a[0]);
    }
    public static int[] test1(){
        int[] a = new int[10];
        a[0]=1;
        try{
            a[0]=2;
            return a;
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            a[0] =3;
            System.out.println("執行了finally");
        }
        return a;
    }
}

代碼二:

public class TestFinally {
    public static void main(String[] args){
        System.out.println(test1());
    }
    public static int test1(){
        int i= 1;
        try{
            i=2;
            return i;
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            i =3;
            System.out.println("執行了finally");
        }
        return i;
    }
}

為什么第一段代碼中的finally覆蓋了try中的return,而第二段代碼就沒有覆蓋呢?

在debug的調試過程中,try中的return語句執行了兩次,在try中第一次執行到return語句時,不會真正的return,即只是會計算return中的表達式,之后將結果保存在一個臨時棧中,接着執行finally中的語句,最后才會從臨時棧中取出之前的結果返回。所以在第一段代碼中,保存的數組的地址,我們在finally中我們對其的內容修改,其實還是返回保存在臨時棧中的地址,只是地址指向堆中的值改變了,而在第二段代碼中,把i=2存在臨時棧中,當我們執行完finally的時候,直接把保存的值i=2直接返回了。

有了這些認識之后,我們討論一下try,catch,finally中有return語句的幾種情況。

第一種:try{}catch(){}finally{}return;

該情況語句后順序執行。(不考慮異常)

第二種:try{return;}catch(){}finally{}return;

該情況為剛才說的題目情況,即執行完try語句塊,將return的值保存在臨時棧中,再執行finally語句塊,之后返回臨時棧中的值。

第三種:try{}catch(){return;}finally{}return;

無異常:執行try,執行finally,再執行return;

有異常:執行完catch語句塊,將return的值保存在臨時棧中,再執行finally語句塊,之后返回臨時棧中的值。

第四種:try{}catch(){}finally{return;}

執行finally中的return語句。

第五種:try{return;}catch(){return;}finally{};

根據有無異常執行和情況二或情況三。

第六種:try{return;}catch(){}finally{return;}

執行完try語句塊,將return的值保存在臨時棧中,再執行finally語句塊,因為finally中有return,所以返回finally中的return值。

第七種:try{}catch(){return;}finally{return;}

執行完catch語句塊,將return的值保存在臨時棧中,再執行finally語句塊,因為finally中有return,所以返回finally中的return值。

第八種:try{return;}catch(){return;}finally{return;}

有異常:執行情況七。

無異常:執行情況六。

 

拋出異常:throw/throws

 

throws 函數聲明

聲明將要拋出何種類型的異常(聲明)。

throws聲明:如果一個方法內部的代碼會拋出檢查異常(checked exception),而方法自己又沒有完全處理掉,則javac保證你必須在方法的簽名上使用throws關鍵字聲明這些可能拋出的異常,否則編譯不通過。

throws是另一種處理異常的方式,它不同於try…catch…finally,throws僅僅是將函數中可能出現的異常向調用者聲明,而自己則不具體處理。

采取這種異常處理的原因可能是:方法本身不知道如何處理這樣的異常,或者說讓調用者處理更好,調用者需要為可能發生的異常負責。

public void 方法名(參數列表)
    throws 異常列表{
 //調用會拋出異常的方法或者:
 throw new Exception();
}

 

throw ----將產生的異常拋出,是拋出異常的一個動作

一般會用於程序出現某種邏輯時程序員主動拋出某種特定類型的異常。

程序執行完throw語句之后立即停止;throw后面的任何語句不被執行,

public static void main(String[] args) { 
     String s = "abc"; 
     if(s.equals("abc")) { 
       throw new NumberFormatException(); 
     } else { 
       System.out.println(s); 
     } 
     //function(); 
}

throw與throws的比較
1、throws出現在方法函數頭;而throw出現在函數體。
2、throws表示出現異常的一種可能性,並不一定會發生這些異常;throw則是拋出了異常,執行throw則一定拋出了某種異常對象。
3、兩者都是消極處理異常的方式(這里的消極並不是說這種方式不好),只是拋出或者可能拋出異常,但是不會由函數去處理異常,真正的處理異常由函數的上層調用處理。

 

使用throw和throws關鍵字需要注意以下幾點:

1.throws的異常列表可以是拋出一條異常,也可以是拋出多條異常,每個類型的異常中間用逗號隔開

2.方法體中調用會拋出異常的方法或者是先拋出一個異常:用throw new Exception() throw寫在方法體里,表示“拋出異常”這個動作。

3.如果某個方法調用了拋出異常的方法,那么必須添加try catch語句去嘗試捕獲這種異常, 或者添加聲明,將異常拋出給更上一層的調用者進行處理

 

四、自定義異常

使用Java內置的異常類可以描述在編程時出現的大部分異常情況。除此之外,用戶還可以自定義異常。用戶自定義異常類,只需繼承Exception類即可。

在程序中使用自定義異常類,大體可分為以下幾個步驟:

1、創建自定義異常類。

2、在方法中通過throw關鍵字拋出異常對象。

3、如果在當前拋出異常的方法中處理異常,可以使用try-catch語句捕獲並處理;否則在方法的聲明處通過throws關鍵字指明要拋出給方法調用者的異常,繼續進行下一步操作。

4、在出現異常方法的調用者中捕獲並處理異常。

舉例自定義異常:

class MyException extends Exception {
    private int detail;
    MyException(int a){
        detail = a;
    }
    public String toString(){
        return "MyException ["+ detail + "]";
    }
}
public class TestMyException{
    static void compute(int a) throws MyException{
        System.out.println("Called compute(" + a + ")");
        if(a > 10){
            throw new MyException(a);
        }
        System.out.println("Normal exit!");
    }
    public static void main(String [] args){
        try{
            compute(1);
            compute(20);
        }catch(MyException me){
            System.out.println("Caught " + me);
        }
    }
}

參考:

https://blog.csdn.net/qq_30816657/article/details/80297646

https://blog.csdn.net/zhanaolu4821/article/details/81012382

https://www.cnblogs.com/hysum/p/7112011.html

https://blog.csdn.net/yongyuai/article/details/79752608

https://blog.csdn.net/ShyTan/article/details/81434219


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM