由于本文旨在探讨Java异常机制的深层原理因此关于异常的使用方法都不做详细说明首先看一段非常熟悉的用于打开一个文件的C程序段
FILE *fp
fp=fopen(filenamerw)
if(fp==NULL){
printf(cannot open file\n)
exit()
}
在这段程序中if条件语句中的一段用来处理没有找到指定文件或者其它原因无法正确打开指定文件可是如果遇到一个责任心不强的程序员他可能认为出现找不到文件的可能性很小或者由于思路集中在程序功能的实现上而忘记了处理这种情况这时程序同样可以正确编译而且一般情况下也不会出现问题但此时这段程序可以肯定说是不够健壮的而且一旦这段程序发生了错误也会让程序员很难发现错误出在哪里在C语言以及其它大多数高级语言中都可以举出很多这种例子
也就是一个函数在使用的时候可能会出现并没有达到这个函数的使用目的的情况哪怕在这段程序的特定使用环境下发生这种异常情况的可能性只有万分之一常用处理的方法就是程序员在需要使用某个函数时必须充分了解可能会有什么原因导致该函数不能正确执行然后加入相应的条件判断语句来进行处理后面将有一个例子说明这个问题
而Java的异常机制就是在处理上述问题中给了程序员非常简单而灵活的方式一般来说其它高级语言主要是让函数使用者来关注该函数可能会出现的异常情况而java则是把这件事情交给方法(和函数对应的概念在Java中称方法)的设计者来做这对于方法的使用者来说带来的方便是不会因为责任心不强或者办事丢三那四会忘了在使用方法时处理可能发生的异常情况而麻烦就是在使用一个可能会发生异常的方法时绝对不能视而不见而必须做出相应的处理也就是说象上述C程序段中如果忘了if程序块这个程序甚至还能蒙过一个外行上司但当使用Java来完成这个功能时只要用到的方法使用了异常机制如果不对可能产生异常的方法进行相应处理java编译器是不会让其通过的
一异常类的组织形式
Java系统类中的方法产生的异常都被组织成异常类(还有Error类不在本文讨论范围)此方法和它相关的异常类通过throws关键字关联在一起并且这些类都必须是Exception类的子类任何一个自己开发的类的方法中如果可能会产生某种异常也可以将这种异常组织成一个异常类但这个异常类同样必须是Exception的子类或孙子类等等
例
/*isLegal于检查数据是否合法当>时视为合法返回合法值
*否则视为不合法抛出异常*/
int isLegal(int dt) throws LowZeroException//这种定义本文中均称为方法与异常通
{ //过throws建立了关联
if(dt>=){
return data
}
else
throw new LowZeroException()
}
/*自已写的异常类继承自Exception*/
class LowZeroException extends Exception
{
public LowZeroException(){
super()
}
}
仔细观察方法isLegal()它体现出的最值得注意的特色是它有两种方式的函数出口一种是通过return语句返回的是方法本身定义的类型的实例另一种是通过throw返回的是异常类的对象实例Java中称之为抛出异常对比一下C中如何处理同样的问题的
int isLegal(int dt) {
if(dt>=){
return data
}
else
return //通过一个特定值来表明出错
}
由于C只能通过return返回函数值所以在处理异常情况时则可能通过以上方式来处理当然这就要求isLegal()函数的使用者必须知道函数中使用返回值来表明出现不合法数据的情况
对比这两种处理方法可以知道java的异常机制把处理异常事件的职能和方法本身的职能通过两个不同出口分离开来
所有这些异常类独立于它具体服务的方法被统一组织成一个类树异常机制就好比高校的后勤社会化一样通过后勤社会化将学校的教学职能和学校的后勤保障分离开来并且后勤集团的组织形式也是独立于学校主体的事实证明这种组织方式不仅提高了服务效率也提高了服务质量整个Java体系中的异常类组织形式如图所示
在例中的isLegal()方法如果在调用过程中没有能正常返回整形数而是在异常产生点产生了异常对象那么这个异常对象由谁来接收并处理它呢?以下就来解答这个问题
二异常的处理过程
Java中由try…catch语法来处理异常将关联有异常类的方法包含在try{}程序块中catch(){}关键字可以使用形参用于和方法产生的异常对象结合当调用某个方法时引起异常事件发生的条件成立便会抛出异常原来的程序流程将会在此方法处中断然后try模块后紧跟的catch中的形参和此异常对象完成了结合继而进入了catch模块中运行具体过程举例说明
例
/*将关联有异常的方法包含在try模块中*/
int myMethod(int dt){
int data =
try{
int data = isLegal(dt)
}catch(LowZeroException e){
Systemoutprintln(发生数据错误!)
}
return data
}
三异常的处理方法
有两种方法处理异常第一种如例将含有异常出口的方法直接放到try块中然后由紧随其后的catch块捕捉第二种是不直接监听捕捉被引用方法的异常而是将这个异常关联传递给引用方法同时监听捕捉工作也相应向上传递
例
int myMethod(int dt)
{
int data =
try{
data = myMethod(dt)
}catch(LowZeroException e){
Systemoutprintln(发生数据错误!)
eprintStackTrace()
}
return data
}
int myMethod(int dt) throws LowZeroException
{
int data = isLegal(dt) //此处引用isLegal()方法但并没有捕捉它的异常
return data
}
从上例中可以看到方法myMethod()与它引用的方法isLegal()产生的异常LowZeroException建立了关联也就是完成了将异常关联的向上传递此时的myMethod()方法体中虽然只有一个return返回语句但它事实上同样有两种方式的函数出口一种是由return返回的整形值另一种则是返回方法名中的throws关键字所指的异常类的实例对象相应的监听捕捉的工作交给了上一层方法myMethod()同样的道理myMethod()也可以将异常通过throws的关联继续向上传递这样的话一旦一个异常被捕捉到时这个异常必有一个传递路径而如果我们在捕捉点的catch程序块中加入printStackTrace()方法便能清楚的看到这个异常是怎样传递过来的例如在例如果有异常被捕捉到eprintStackTrace()打印出来的结果将是
LowZeroException
at ExampleisLegal
at Example myMethod
at ExamplemyMethod
at Example main
从上结果中我们可以看到从LowZeroException异常产生点即包含throw new LowZeroException()子句的方法开始然后一直追溯到产生当前线程的方法(注意printStackTrace()并不是追溯到捕捉点结束而是到产生当前线程的方法结束)异常产生点产生的LowZeroException异常对象首先被赋给了isLegal()关联的LowZeroException类的无名引用然后继续赋给myMethod()关联的LowZeroException类的无名引用再继续赋给myMethod()中的catch块中的形参e最后在这里被处理掉这个异常对象随即消失可以说catch(){}就是异常对象的生命终结点
另外还要注意一点方法与异常的关联可以一直向上传递当传递到与main方法关联后即在main()方法的定义中使用了throws Exception这时除了虚拟机没有其它方法能够引用main()方法且在程序中可能看不到try…catch程序块但并不会产生错误因为此时虚拟机会捕捉异常并且会默认的调用printStackTrace()方法打印出异常路径总之只要一个方法关联了异常可以将这个异常关联向上传递但是最终必须使用catch来终止异常或者一直传递到main()方法交给Java虚拟机来结束异常对象的生命否则是通不过编译的
四使用异常机制的需要注意的几点
一个方法中可能会产生多种不同的异常你可以设置多个异常抛出点来解决这个问题
异常对象从产生点产生后到被捕捉后终止生命的全过程中实际上是一个传值过程所以你可以根据需要来合理的控制检测到异常的粒度例如在例中如果你并不需要知道具体产生的是LowZeroException异常那么你可以使用异常的公共父类Exception来结合异常对象即catch(Exception e){…}同样在异常与方法关联的传递过程中也可以根据需要控制关联异常的粒度即throws后面跟上异常对象的父类名
异常机制中还有一种特殊情况――RuntimeException异常类这个异常类和它的所有子类都有一个特性就是异常对象一产生就被Java虚拟机直接处理掉即在方法中出现throw 子句的地方便被虚拟机捕捉了因此凡是抛出这种运行时异常的方法在被引用时不需要有try…catch语句来处理异常