Java異常處理很難嗎?BAT大廠的架構師是怎麼處理Java異常的?

異常是運行時在代碼序列中引起的非正常狀態。在不支持異常處理的計算機語言中,必須手動檢查和處理錯誤,Java語言則採用面向對象的方式管理運行時錯誤

Java異常處理很難嗎?BAT大廠的架構師是怎麼處理Java異常的?

一、基礎知識

Java異常是用來描述在一段代碼中發生的異常情況的對象。當出現引起異常的情況時,就會創建用來表示異常的對象,並在引起異常的方法中拋出異常對象。方法可以選擇自己處理異常或者傳遞異常交由其他方法來處理

Java異常處理通過五個關鍵字進行管理:try、catch、throw、throws、finally

在try代碼快中封裝可能發生異常的程序語句,對這些語句進行監視。如果在try代碼塊中發生異常,就會將異常拋出。代碼使用catch來捕獲異常,並可以定義一些方法來處理異常。系統生成的異常由Java運行時系統自動拋出。為了手動拋出異常,需要使用throw關鍵字。從方法中拋出的任何異常都必須通過一條throws子句進行指定。在try代碼塊結束之後必須執行的所有代碼則需要放在finally代碼塊中

二、異常類型

所有異常類型都是內置類Throwable的子類,Throwable的兩個子類將異常分為兩個不同的分支。一個分支是Exception類,這個類既可以用於用戶程序當前捕獲的異常情況,也可以用於創建自定義異常類型的子類。另一個分支是Error類,該類定義了在常規環境下不希望由程序捕獲的異常。Error類型的異常由Java運行時系統使用,以指示運行時系統本身發生了某些錯誤

三、未捕獲的異常

沒有被程序捕獲的所有異常,最終都會交由Java運行時系統提供的默認處理程序捕獲。默認處理程序會顯示一個描述異常的字符串,輸出異常發生的堆棧蹤跡並終止程序

例如,運行如下代碼:

<code>public class Demo {    public static void main(String[] args) {        int a=2/0;    }}/<code>

生成的異常信息:

<code>Exception in thread "main" java.lang.ArithmeticException: / by zero    at Demo.main(Demo.java:5)/<code>

拋出的異常的類型是Exception的子類ArithmeticException,即算術異常,更具體地描述了發生的錯誤類型。Java提供了一些能與可能產生的各種運行時錯誤相匹配的內置異常類型

Java異常處理很難嗎?BAT大廠的架構師是怎麼處理Java異常的?

四、使用try和catch

進行主動的異常處理有兩個優點:第一,語序允許修復錯誤;第二,阻止程序自動終止

為了主動處理運行時錯誤,可以在try代碼塊中封裝希望監視的代碼,之後通過catch子句指定希望捕獲的異常類型。 例如,參照如下代碼:

<code>    public static void main(String[] args) {        try{            int a=10/0;            System.out.println("輸出語句1");        }catch(ArithmeticException e){            System.out.println("輸出語句2");        }        System.out.println("輸出語句3");    }/<code>

輸出結果是:

<code>    輸出語句2    輸出語句3/<code>

代碼在進行除零操作時發生了異常,此時“輸出語句1”將無法得到被調用的機會。一旦拋出異常,程序控制就會從try代碼塊中轉移出來,進入catch代碼塊中。執行了catch語句後,就會繼續運行整個try/catch代碼塊的下一行

五、多條catch子句

在有些情況下,一個代碼塊可能會引發多個異常。對於這種情況,需要指定兩條或多條catch子句,用於捕獲不同類型的異常。當拋出異常時,按順序檢查每條catch語句,執行類型和異常相匹配的第一條catch子句,忽略其他catch子句,並繼續執行try/catch代碼塊後面的代碼

需要注意的是,異常子類必須位於異常超類之前,因為使用了某個超類的catch語句會捕獲這個超類及其所有子類的異常。因此,如果子類位於超類之後的話,永遠也不會到達子類。不可到達的代碼會被編譯器提示錯誤

參照如下代碼,通過Math的靜態方法random來隨機產生0和1兩個隨機數,生成的不同數值就會引發不同的異常,分別由不同的catch子句進行處理

<code>    public static void main(String[] args) {            int random=(int) Math.round(Math.random());        try{            int a=10/random;            int[] array={10};            array[random]=1;        }catch (ArithmeticException e) {            System.out.println("ArithmeticException");        }catch (ArrayIndexOutOfBoundsException e) {            System.out.println("ArrayIndexOutOfBoundsException");        }        System.out.println("代碼塊結束");    }/<code>

此外,也可以通過多重捕獲的方式來使用相同的catch子句捕獲兩個或更多個異常。在catch子句中使用或運算符(|)分隔每個異常,每個多重捕獲參數都被隱式地聲明為final類型

<code>    public static void main(String[] args) {            int random=(int) Math.round(Math.random());        try{            int a=10/random;            int[] array={10};            array[random]=1;        }catch(ArithmeticException | ArrayIndexOutOfBoundsException e){            System.out.println("兩個異常之一");        }    }/<code>

六、throw

在之前的例子中,捕獲的異常都是由Java運行時系統拋出的異常,可以通過throw語句顯式地拋出異常

throw的一般形式如下所示:

<code>    throw ThrowableInstance/<code>

ThrowableInstance必須是Throwable或其子類類型的對象。throw語句之後的執行流程會立即停止,所有的後續語句都不會執行,然後按順序依次檢查所有的catch語句,檢查是否和異常類型相匹配。如果沒有找到匹配的catch語句,那麼默認的異常處理程序會終止程序並輸出堆棧蹤跡

例如,運行以下代碼,將得到輸出結果:“NullPointerException”

<code>public class Demo {    public static void demo(){        throw new NullPointerException("NullPointer");    }    public static void main(String[] args) {        try{            demo();        }catch (NullPointerException e) {            System.out.println("NullPointerException");        }    }}/<code>

但如果catch子句的異常與throw拋出的異常類型不匹配時,異常將由Java默認的異常處理程序來處理

例如,運行以下代碼:

<code>public class Demo {    public static void demo(){        throw new NullPointerException("NullPointer");    }    public static void main(String[] args) {        try{            demo();        }catch (ArrayIndexOutOfBoundsException e) {            System.out.println("ArrayIndexOutOfBoundsException");        }    }}/<code>

運行結果是:

<code>Exception in thread "main" java.lang.NullPointerException: NullPointer    at Demo.demo(Demo.java:4)    at Demo.main(Demo.java:9)/<code>
Java異常處理很難嗎?BAT大廠的架構師是怎麼處理Java異常的?

七、throws

如果方法可能引發自身不進行處理的異常,就必須通過throws關鍵字來向方法的調用者指明要拋出的異常類型。 方法可能拋出的所有異常都必須在throws子句中進行聲明,否則將產生編譯時錯誤

例如,參照以下代碼,該程序試圖拋出無法匹配的異常,因為程序沒有指定throws子句來聲明這一事實,所以程序無法編譯

<code>public class Demo {    public static void demo(){        throw new IllegalAccessException("IllegalAccess");    }    public static void main(String[] args) {            demo();    }}/<code>

為了使代碼能夠運行,需要將之進行如下修改

<code>public class Demo {    public static void demo() throws IllegalAccessException{        throw new IllegalAccessException("IllegalAccess");    }    public static void main(String[] args) {            try {                demo();            } catch (IllegalAccessException e) {                System.out.println(e.getMessage());            }    }}/<code>

八、finally

當拋出異常後,方法的執行流程將不會按照原先的順序進行,這對於某些方法來說是個問題。例如,如果在方法中打開了一個文件,我們並不希望因為拋出了異常導致關閉文件的代碼被跳過而不執行。finally關鍵字就是來解決這種情況的。 使用finally可以創建一個代碼塊,該代碼塊會在執行try/catch代碼塊之後執行,且在執行try/catch代碼塊後面的代碼之前執行。不管是否有異常拋出,都將執行finally代碼塊

只要方法從try/catch代碼塊內部返回到調用者,不管是通過未捕獲的異常還是使用顯式的返回語句,都會在方法返回之前執行finally子句

例如,如下的輸出結果將是:2

<code>public class Demo {    public static int demo(){        try{            int a=10/0;            System.out.println(a);        }catch(Exception e){            return 1;        }finally {            return 2;        }    }    public static void main(String[] args) {        System.out.println(demo());    }}/<code>

九、鏈式異常

鏈式異常用於為異常關聯另一個異常,第二個異常用於描述當前異常產生的原因。例如,某個方法從文件讀取數值來作為除法運算的除數,由於發生了I/O錯誤導致獲取到的數值是0,從而導致了ArithmeticException異常

如果想要讓代碼調用者知道背後的原因是I/O錯誤,使用鏈式異常就可以來處理這種情況以及其他存在多層異常的情況

為了使用鏈式異常,Throwable有兩個構造函數和兩個方法用於處理這種情況

兩個構造函數:

<code>    Throwable(Throwable cause)    Throwable(String message, Throwable cause)/<code>

cause即是用於指定引發當前異常的背後原因,message則可以用於同時指定異常描述

兩個方法:

<code>    Throwable getCause()    Throwable initCause(Throwable cause)/<code>

getCause() 方法用來返回引發當前異常的異常,如果不存在背後異常則返回null

initCause(Throwable cause) 方法將cause和明面上的異常關聯到一起,並返回對異常的引用。因此可以在創建異常之後將異常和背後異常關聯到一起。但是,背後異常只能設置一次,即initCause(Throwable cause) 方法只能調用一次。此外,如果通過構造函數設置了背後異常,也不能再使用該方法來設置背後異常了

例如,參照如下代碼:

<code>public class Demo {    public static void demo(){        NullPointerException nullPointerException=new NullPointerException("nullPointer");        nullPointerException.initCause(new ArithmeticException("Arithmetic"));        throw nullPointerException;    }    public static void main(String[] args) {        try {            demo();        } catch (Exception e) {            System.out.println(e.getMessage());            System.out.println(e.getCause().getMessage());        }    }}/<code>

運行結果是:

<code>    nullPointer    Arithmetic/<code>

鏈式異常可以包含所需要的任意深度,但是,過長的異常鏈可能是一種不良的設計

Java程序員福利:我把2019近一年經歷過的Java崗位面試,和一些刷過的面試題都做成了PDF,PDF都是可以免費分享給大家的,關注私信我:【101】,免費領取!


分享到:


相關文章: