[改善Java代碼]不要在構造函數中拋出異常


Java的異常機制有三種:

一.Error類以及其子類表示的是錯誤,它是不需要程序員處理也不能處理的異常.比如VirtualMachineError虛擬機錯誤,ThreadDeath線程僵屍等.

二.RuntimeException類及其子類表示的是非受檢查異常,是系統可能會拋出的異常,程序員可以去處理,也可以不去處理,最經典的就是NullPointerException空指針異常和IndexOutOfBoundsException越界異常.

三.Exception類及其子類(不包含非受檢查異常)表示的是受檢異常,這是程序員必須處理的異常,不處理則程序不能通過編譯,比如IOException表示I/O異常,SQLException表示數據庫訪問異常.

我們知道 一個對象的創建,需要經過內存分配,靜態代碼初始化,構造函數執行等過程,對象生成的關鍵步驟是構造函數,那是不是允許在構造函數中拋出異常呢?從Java語法上來說,完全可以在構造函數中拋出異常,三類 異常都可以,

但是從系統設計和開發的角度來分析,則盡量不要在構造函數中拋出異常,我們以三種不同類型的異常來說明:

(1)構造函數拋出錯誤是程序員無法處理的

(2)構造函數不應該拋出非受檢查異常

看如下的例子:

class Person{
    public Person(int _age){
        //不滿18歲得用戶對象不能建立
        if(_age<18){
            //throw new RuntimeException("年齡必須大於18歲。");
        }        
    }
    //看限制級的電影
    public void seeMovie(){
        System.out.println("看限制級電影");
    }
}

代碼的意圖很明顯,不滿18歲的用戶根本就不會生成一個Person實例對象,沒有對象,類行為seeMovie方法就不可以執行,想法很好,但這會導致不可預測的結果,比如我們這樣引用Person類.

    public static void main(String[] args) {
        while(true){
            Person p = new Person(17);
            p.seeMovie();
        }

        /*其他的邏輯處理*/
    }

很明顯,p對象不能建立,因為是一個RuntimeException異常,開發人員可以捕捉也可以不捕捉,代碼看上去邏輯很正確,沒有任何的瑕疵,但是事實上,這段程序會拋出異常,無法執行,這段代碼給了我們兩個警示: 

  ①加重了上層代碼編寫者的負擔.

  ②后續代碼不會執行.

(3)構造函數盡可能不要拋出受檢查異常

//父類
class Base{
    //父類拋出IOException
    public Base() throws IOException{
        throw new IOException();
    }
    //父類方法拋出Exception
    public void method() throws Exception{
        
    }
}
//子類
class Sub extends Base{
    //子類拋出Exception異常
    public Sub() throws Exception {
    }
    //子類方法的異常類型必須是覆寫方法的子類型
    @Override
    public void method() throws IOException{
        
    }
}

就這么一段代碼,展示了在構造函數中拋出受檢查異常的三個不利方面.

①導致子類代碼膨脹

  上面的例子中,子類的無參構造函數不能省略,原因是父類的無參構造函數拋出了IOException異常,子類的無參構造函數默認調用的是父類的構造函數,所以子類的無參構造函數必須拋出IOException或其父類.

 ②違背了里氏替換原則

    里氏替換原則說"父類能出現的地方子類就可以出現,而且將父類替換為子類也不會產生任何異常",那我們回過頭來看看Sub類是否可以替換Base類,比如我們的上層代碼是這樣寫的:

    public static void main(String[] args) {
        try{
            Base base = new Base();
        }catch(IOException e){
            //異常處理
        }
    }

然后我們希望把new Base()替換成new Sub(),而且代碼能夠正常編譯和運行.非常可惜編譯不通過..原因是Sub的構造函數拋出了Exception異常,它比父類的構造函數拋出的異常范圍要寬,必須增加新的catch塊才能解決.

 1 import java.io.IOException;
 2 
 3 public class Client {
 4     public static void main(String[] args) {
 5         try{
 6             Base base = new Base();
 7             //Base base = new Sub();這樣是編譯不通過的,因為Sub的構造拋出Exception,它比父類的構造函數
 8             //拋出的異常范圍要寬,必須增加新的catch塊才能解決.
 9         }catch(IOException e){
10             //異常處理
11         }
12     }
13 }
14 
15 class Base{
16     //父類拋出IOException
17     public Base() throws IOException{
18         throw new IOException();
19     }
20 }
21 
22 class Sub extends Base{
23     //子類拋出Exception異常
24     public Sub() throws Exception {
25 
26     }
27 }

可能讀者會問,為什么Java的構造函數允許子類的構造函數拋出更廣泛的異常類呢?這正好與類方法的異常機制相反,類方法的異常是這樣要求的:

class Base{

    //父類方法拋出Exception
    public void method() throws Exception{
        
    }
}

class Sub extends Base{
    //子類方法的異常類型必須是覆寫方法的子類型
    @Override
    public void method() throws IOException{
        
    }
}

子類方法可以拋出多個異常,但是都必須是覆寫方法的子類型,對我們的例子來說,Sub類的method方法拋出的異常必須是Exception的子類或者Exception類,這是Java覆寫的要求.構造函數之所以與此相反,是因為構造函數沒有覆寫的概念,只是構造函數間的引用調用而已,所以在構造函數中拋出受檢查異常會違背里氏替換原則,使我們的程序缺乏靈活性. 

   ③子類構造函數擴展有限

以上匯總起來就是:非受檢查異常不要拋出,拋出了對人對己都是有害的...受檢查異常盡量不拋出.總之一句話:在構造函數中盡可能的不出現異常.

 


免責聲明!

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



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