檢查型異常的問題

Java的異常分爲兩種異常,一種是檢查型異常(checked exception),如IOException等。另一種是非檢查型異常(unchecked exception),也叫運行時異常,如IllegalArgumentException等。

檢查型異常和非檢查型異常的區別在於,當一個方法想要拋出非檢查型異常時,可以不在方法頭拋出;而如果拋出檢查型異常,則必須在方法頭進行聲明。當一個方法調用另一個拋出了非檢查型異常的時候,可以不捕獲那個方法拋出的異常;而當調用的是拋出檢查型異常的方法,則必須捕獲這個異常,或是在自身方法頭也拋出這個異常。

關於檢查型異常的問題,也是編程界著名的公案。有的專家認爲檢查型異常是不可或缺的,可以對異常進行很好的編程文檔化。也有的專家認爲檢查型異常是畫蛇添足,把異常處理複雜化了。比如跟Java很類似的C#中,就是沒有檢查型異常的。

最近看一些代碼,裏面調用了JDK中反射獲取對象的Field值的方法:

	/**
     * @exception IllegalAccessException    if this {@code Field} object
     *              is enforcing Java language access control and the underlying
     *              field is inaccessible.
     * @exception IllegalArgumentException  if the specified object is not an
     *              instance of the class or interface declaring the underlying
     *              field (or a subclass or implementor thereof).
     * @exception NullPointerException      if the specified object is null
     *              and the field is an instance field.
     * @exception ExceptionInInitializerError if the initialization provoked
     *              by this method fails.
     */
    @CallerSensitive
    public Object get(Object obj)
        throws IllegalArgumentException, IllegalAccessException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        return getFieldAccessor(obj).get(obj);
    }

多餘的註釋我刪掉了。看這個方法拋出了兩個異常中的其中一個IllegalAccessException,這是一個檢查型異常。拋出它的時機,這個方法註釋寫的很清楚:
如果這個field是強制進行Java語言訪問控制,並且實際定義的field是不可訪問的,那麼就會拋出這個異常。

Java語言訪問控制,簡單理解就是訪問控制符,主要有以下幾種:
public: 可被所有類訪問
protected: 可被同一個包或子類訪問
private: 只能自己訪問
無控制符: 被同一個包的類訪問

所以我定義瞭如下的一個類:

public class Model {
    private int i;

    public Model(int i) {
        this.i = i;
    }
}

以及:

public class ExceptionTest {
    public static void main(String[] args) {
        Model model = new Model(1);
        Field[] fields = model.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (field.getName().equals("i")) {![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20190615142718710.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2RicWIwMDc=,size_16,color_FFFFFF,t_70)
                try {
                    int i = (int) field.get(model);
                    System.out.println(i);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

這個類是獲取i這個屬性,並打印出來。
在這裏插入圖片描述
可以看出,因爲i是private的,所以在另一個類裏沒有通過反射獲取它的權限,因此捕獲了被拋出的IllegalAccessException。

如果加上一行代碼,變成下面這樣:

public class ExceptionTest {
    public static void main(String[] args) {
        Model model = new Model(1);
        Field[] fields = model.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (field.getName().equals("i")) {
                field.setAccessible(true);
                try {
                    int i = (int) field.get(model);
                    System.out.println(i);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

輸出
在這裏插入圖片描述
因爲調用setAccessible方法,關閉了強制的Java語言訪問控制,所以能獲取到i的值。

這裏我要強調的是,field這個get方法的註釋裏明確說了,只有當沒有訪問這個字段的權限的時候,纔會拋出IllegalAccessException。因此,當我沒有調用setAccessible這個方法時,我是預期到可能會捕獲這個異常的。但是當我調用了這個方法的時候,我可以確定不會拋出這個異常,那我try catch這個異常,或是再拋出這個異常有什麼意義呢?

因此我這裏想說的是,檢查型異常某種程度上影響了代碼的結構,以及開發者對異常的靈活處理,假如我們定義某個接口的方法,聲明瞭會拋出某個異常,並且規定好了在什麼情況下會拋出什麼異常,那麼應該由調用者來決定是否捕獲異常,或者是在哪一層捕獲異常。可能開發者能根據它傳入參數的情況,或是代碼運行的環境,或是當前代碼的上下文,就能確定是否會拋出這個異常。那這種時候,就沒必要捕獲異常。或是在調用方法的更上層捕獲異常。如果是非檢查型異常,那就可以實現這個效果。但如果是檢查型異常,就必須捕獲,或是再次拋出這個異常了。

檢查型異常和非檢查型異常的爭論沒有定論,限於水平有限,就不展開討論了,只是把工作中遇到的一些問題和思考記錄下來。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章