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這個異常,或是再拋出這個異常有什麼意義呢?
因此我這裏想說的是,檢查型異常某種程度上影響了代碼的結構,以及開發者對異常的靈活處理,假如我們定義某個接口的方法,聲明瞭會拋出某個異常,並且規定好了在什麼情況下會拋出什麼異常,那麼應該由調用者來決定是否捕獲異常,或者是在哪一層捕獲異常。可能開發者能根據它傳入參數的情況,或是代碼運行的環境,或是當前代碼的上下文,就能確定是否會拋出這個異常。那這種時候,就沒必要捕獲異常。或是在調用方法的更上層捕獲異常。如果是非檢查型異常,那就可以實現這個效果。但如果是檢查型異常,就必須捕獲,或是再次拋出這個異常了。
檢查型異常和非檢查型異常的爭論沒有定論,限於水平有限,就不展開討論了,只是把工作中遇到的一些問題和思考記錄下來。