详解 异常(exception) 二
异常大的分类分为检测性异常(checked exception)和非检测性异常(unchecked exception)。其中检测性异常是指在编译器就能够发现的异常。
非检测性异常又包括Error 和 Exception 其中Error代表了非程序内部的异常,即来自于外部的异常:比如: 硬盘破坏、系统故障等。 Exception是指程序内部的异常:一般是指程序是逻辑出了问题,或者 不正确的使用了API造成的异常。
不管是检测性异常还是非检测性异常 都是继承自 Throwable类。
一:方法抛出异常 上一节中学习了在ListOfNumbers类中的writeList方法写异常处理程序。有时候在方法内部捕获是合适的。但是在其他有些情况下,它是更合理的让一个方法进一步的调用栈去处理这个异常(it's better to let a method further up the call stack handle the exception),即不在该方法中处理异常,而是抛给调用这个方法的方法去处理。例如,如果你提供ListOfNumbers类作为包类的一部分,你有可能不预先得知所有使用该包的用户,即你不知道哪些其他的用户会使用这个类,有可能其他包下的类会使用这个类,即使用这个类的情况不确定,在这种情况下,不捕获异常也许是一个更好的方式,而是让进一步调用这个方法的调用栈去处理它。 如果writeList()方法不捕获出现在其内部的检测性异常,writeList方法必须指定它能够抛出这些异常,下面修改writeList方法抛出一个异常代替捕获异常,以下是writeList方法不能通过编译的原始版本。
Throwable类及其子类结构图3:Error类当JVM出现动态链接失败或者其他的难处理的失败时,JVM会抛出一个Error,简单的程序通常不能够捕获或者throw Errors。
4:Exception 类大部分程序抛出或者捕获的对象源自于Exception类,一个Exception类说明了一个问题的出现,但是它不是一个严重的系统级问题,大部分写程序的时候和Errors的处理方式相反,是抛出或者捕获这类异常。
java平台定义了很多Exception的子类,这些子类显示了可能出现的各种各样的异常,例如IllegalAccessException说明没有找到一个特定的方法。NegativeArraySizeException说明了一个程序视图创建一个负数大小的数组。Exception的一个子类RuntimeException是说明了不正确的使用API,通常是使用过程中逻辑出现了问题,例如NullPointerException是一个运行时异常,当一个方法试着访问一个null引用的时候会出现这种异常。二:异常链(Chained Exceptions)程序中经常通过抛出一个异常的方法来回应一个异常。事实上是第一个异常引起了第二个异常,知道当一个异常引起了另外一个异常这个信息非常重要。异常链(Chained Exceptions)会帮助我们做这件事情。
下面是一些在Throwable类中支持链式异常的方法和构造方法
异常类继承的示例2:选择一个子类能够使用LinkedListException的地方也能够使用一些其他的子类,当熟悉这些子类以后,很多情况下使用使用的异常和LinkedListException父类完全不相关,因此LinkedListException父类是一个例外,一般不用。大部分写的程序中抛出异常对象的类型是Exceptions,Errors通常是一种较严重的情况,如果系统硬件破坏,系统故障等,这些都将阻止JVM的运行。
注意:为了代码的可读性,在所有间接或者直接继承自Exception类的子类中追加异常字符串名字是一个很好的编程习惯。四:关于非检测性异常的争论(Unchecked Exceptions — The Controversy)
由于java语言不需要方法捕获或者指定特定的非检测性异常(RuntimeException,Error,和它们的子类),程序员也许有兴趣在代码中仅仅抛出非检测性异常,或者让它们所有的异常子类继承自RuntimeException,这两种方式都让程序员没有编译错误和没有捕获指定异常的干扰,虽然这似乎对程序员来说很方便,但是当别人使用你写的类的时候也许会引起一些问题。因此不建议这样操作。
为什么设计者强制指定所有在一个方法内抛出的检测性异常呢?在一个方法上抛出异常作为该方法公共编程接口的一部分。方法的调用者必须知道方法抛出了什么异常,为了让调用者决定关于这个方法需要怎么做,在方法的编程接口中,异常是作为方法的参数和返回值的一部分。
下面一个问题是:如果一个方法API文档写的很完善,包括了它抛出的异常,为什么在这个方法中不指定运行时异常呢?运行时异常说明了程序运行时出现的问题,对于这些异常问题客户端代码不能合理的从这些异常问题中恢复,或者不能才起一些其他的处理方式处理这些异常问题。这些异常问题包括算术运算异常(例如除0);指向(引用)异常(例如试图访问一个指向空对象的引用);索引异常(例如试图访问一个太大或者太小的数组的索引)。
运行时异常能够出现在程序中的很多地方,换句话来说,就是有很多各式各样的运行时异常,在每一个方法声明中添加运行时异常将减少程序的透明性(使程序不是很清晰),为了使程序更加清晰、明了,因此编译器不是必须让程序员捕获或者指定特定的异常(即使你能够捕获或指定)。
常见的抛出运行时异常的例子是当用户错误的调用一个方法的时候,例如:如果一个参数是不正确的为null,方法能够检查到, 如果一个参数为null,方法也许会抛出一个NullPointerException,该异常是个非检测性异常(unchecked exception)。
一般来说:不会抛出运行时异常或者简单的创建一个RuntimeException的子类,因为你不想在方法中指定需要抛出异常而感到烦恼。
下面是一条重要的规则:如果客户端代码能够合理的从异常中恢复,该异常就是一个检测性异常(checked exception),如果不能合理的从异常中恢复,该异常就是一个非检测性异常(unchecked exception)。
五:使用异常的优点以上已经了解了异常,以及如何使用异常,下面来看一下在程序中使用异常的好处。
1:从业务规则代码中分离出错误处理代码当在一个主要逻辑代码中出现非常规性事件,异常从代码中分离出来,提供有意义的信息。
在传统的编程中,错误检测、报告和处理经常让代码感觉到混乱,例如:下面是一个读文件到内存中的伪代码方法。try {} catch (Exception e) { } catch (ArithmeticException a) { }以上代码错误,第一个捕获异常已经捕获了所有的异常,第二个就多余了,顺序颠倒一下是可以的。
a:int[] A ; A[0]=0; -- 编译错误,数组没有初始化,不能够通过编译。b:JVM开始运行你的程序,但是JVM不能找到java平台的类 --- errorc:程序读流,到达流的末端, -------no exceptiond:在到达流的末端后和关闭流之前,程序试着再一次读取流中的信息, checked exception (检测性异常)