检查型异常的问题

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这个异常,或是再抛出这个异常有什么意义呢?

因此我这里想说的是,检查型异常某种程度上影响了代码的结构,以及开发者对异常的灵活处理,假如我们定义某个接口的方法,声明了会抛出某个异常,并且规定好了在什么情况下会抛出什么异常,那么应该由调用者来决定是否捕获异常,或者是在哪一层捕获异常。可能开发者能根据它传入参数的情况,或是代码运行的环境,或是当前代码的上下文,就能确定是否会抛出这个异常。那这种时候,就没必要捕获异常。或是在调用方法的更上层捕获异常。如果是非检查型异常,那就可以实现这个效果。但如果是检查型异常,就必须捕获,或是再次抛出这个异常了。

检查型异常和非检查型异常的争论没有定论,限于水平有限,就不展开讨论了,只是把工作中遇到的一些问题和思考记录下来。

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