Java抛出异常

来源:互联网 发布:手机淘宝店铺名怎么改 编辑:程序博客网 时间:2024/05/15 23:53
首先看一段非常熟悉的用于打开一个文件的C程序段: FILE *fp; fp=fopen(filename,"rw"); if(fp==NULL){ printf("cannot open file\n"); exit(0); } 在这段程序中,if条件语句中的一段用来处理没有找到指定文件,或者其它原因无法正确打开指定文件。可是如果遇到一个责任心不强的程序员,他可能认为出现找不到文件的可能性很小,或者由于思路集中在程序功能的实现上而忘记了处理这种情况。这时程序同样可以正确编译,而且一般情况下也不会出现问题。但此时这段程序可以肯定说是不够健壮的,而且一旦这段程序发生了错误也会让程序员很难发现错误出在哪里。在C语言以及其它大多数高级语言中都可以举出很多这种例子。 也就是一个函数在使用的时候,可能会出现并没有达到这个函数的使用目的的情况,哪怕在这段程序的特定使用环境下发生这种异常情况的可能性只有万分之一。常用处理的方法就是,程序员在需要使用某个函数时必须充分了解可能会有什么原因导致该函数不能正确执行,然后加入相应的条件判断语句来进行处理。后面将有一个例子说明这个问题。 而Java的" 异常机制"就是在处理上述问题中给了程序员非常简单而灵活的方式。一般来说,其它高级语言主要是让函数使用者来关注该函数可能会出现的异常情况,而 java则是把这件事情交给方法(和函数对应的概念,在Java中称方法)的设计者来做。这对于方法的使用者来说带来的方便是不会因为责任心不强,或者办事丢三那四,会忘了在使用方法时处理可能发生的异常情况。而麻烦就是,在使用一个可能会发生异常的方法时,绝对不能视而不见,而必须做出相应的处理。也就是说象上述C程序段中,如果忘了if程序块,这个程序甚至还能蒙过一个外行上司,但当使用Java来完成这个功能时,只要用到的方法使用了"异常"机制,如果不对可能产生"异常"的方法进行相应处理,java编译器是不会让其通过的。 一、"异常类"的组织形式 Java系统类中的方法产生的异常都被组织成"异常类"(还有Error类,不在本文讨论范围),此方法和它相关的"异常类"通过throws 关键字关联在一起,并且这些类都必须是Exception类的子类。任何一个自己开发的类的方法中如果可能会产生某种异常,也可以将这种异常组织成一个" 异常类",但这个"异常类"同样必须是Exception的子类,或孙子类等等。 例1: /*isLegal于检查数据是否合法,当>0时视为合法,返回合法值, *否则视为不合法,抛出"异常".*/ int isLegal(int dt) throws LowZeroException//这种定义本文中均称为方法与"异常"通 { //过throws建立了关联 if(dt>=0){ 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>=0){ return data; } else return -1;//通过一个特定值来表明出错 } 由于C只能通过return返回函数值,所以在处理异常情况时则可能通过以上方式来处理。当然这就要求isLegal()函数的使用者必须知道函数中使用返回值-1来表明出现不合法数据的情况。 对比这两种处理方法,可以知道java的"异常机制"把处理异常事件的职能和方法本身的职能通过两个不同出口分离开来。 所有这些"异常类"独立于它具体服务的方法被统一组织成一个类树。"异常机制"就好比高校的后勤社会化一样,通过后勤社会化将学校的教学职能和学校的后勤保障分离开来,并且后勤集团的组织形式也是独立于学校主体的。事实证明,这种组织方式不仅提高了服务效率,也提高了服务质量。整个Java体系中的"异常类"组织形式如图1所示: 在例1中的isLegal()方法如果在调用过程中没有能正常返回整形数,而是在"异常"产生点产生了"异常"对象,那么这个"异常"对象由谁来接收,并处理它呢?以下就来解答这个问题。 二、"异常"的处理过程 Java中由try…catch语法来处理"异常",将关联有"异常类"的方法包含在try{}程序块中,catch(){}关键字可以使用形参,用于和方法产生的"异常"对象结合。当调用某个方法时,引起异常事件发生的条件成立,便会抛出"异常",原来的程序流程将会在此方法处中断,然后 try模块后紧跟的catch中的"形参"和此异常对象完成了结合,继而进入了catch模块中运行。具体过程举例说明: 例2: /*将关联有异常的方法包含在try模块中*/ int myMethod(int dt){ int data = 0; try{ int data = isLegal(dt); }catch(LowZeroException e){ System.out.println("发生数据错误!"); } return data; } 三、"异常"的处理方法 有两种方法处理"异常":第一种如例2,将含有"异常"出口的方法直接放到try块中,然后由紧随其后的catch块捕捉。第二种是不直接监听捕捉被引用方法的"异常",而是将这个"异常"关联传递给引用方法,同时监听捕捉工作也相应向上传递。 例3: int myMethod2(int dt) { int data = 0; try{ data = myMethod(dt) }catch(LowZeroException e){ System.out.println("发生数据错误!"); e.printStackTrace(); } return data; } int myMethod(int dt) throws LowZeroException { int data = isLegal(dt); //此处引用isLegal()方法,但并没有捕捉它的"异常" return data; } 从上例中可以看到方法myMethod()与它引用的方法isLegal()产生的"异常"LowZeroException建立了关联,也就是完成了将"异常"关联的向上传递,此时的myMethod()方法体中虽然只有一个return返回语句,但它事实上同样有两种方式的函数出口,一种是由return返回的整形值,另一种则是返回方法名中的throws关键字所指的"异常类"的实例对象。相应的,监听捕捉的工作交给了上一层方法 myMethod2()。同样的道理,myMethod2()也可以将"异常"通过throws的关联继续向上传递。这样的话,一旦一个"异常"被捕捉到时,这个"异常"必有一个传递路径,而如果我们在捕捉点的catch程序块中加入printStackTrace()方法,便能清楚的看到这个"异常"是怎样传递过来的。例如在例3如果有"异常"被捕捉到,e.printStackTrace()打印出来的结果将是: LowZeroException: at Example.isLegal at Example myMethod at Example.myMethod2 at Example main 从上结果中我们可以看到,从LowZeroException"异常"产生点,即包含throw new LowZeroException();子句的方法开始,然后一直追溯到产生当前线程的方法(注意:printStackTrace()并不是追溯到捕捉点结束,而是到产生当前线程的方法结束)。"异常"产生点产生的LowZeroException"异常"对象,首先被赋给了isLegal()关联的 LowZeroException类的无名引用,然后继续赋给myMethod()关联的LowZeroException类的无名引用,再继续赋给 myMethod2()中的catch块中的形参e,最后在这里被处理掉,这个"异常"对象随即消失。可以说,catch(){}就是"异常"对象的生命终结点。 另外还要注意一点,方法与"异常"的关联可以一直向上传递,当传递到与main方法关联后,即在main()方法的定义中使用了throws Exception,这时除了虚拟机没有其它方法能够引用main()方法,且在程序中可能看不到try…catch程序块,但并不会产生错误,因为此时虚拟机会捕捉"异常",并且会默认的调用printStackTrace()方法打印出"异常"路径。总之只要一个方法关联了"异常",可以将这个"异常 "关联向上传递,但是最终必须使用catch来终止"异常",或者一直传递到main()方法交给Java虚拟机来结束"异常"对象的生命,否则是通不过编译的。 四、使用"异常机制"的需要注意的几点 1.一个方法中可能会产生多种不同的异常,你可以设置多个"异常"抛出点来解决这个问题。 2."异常"对象从产生点产生后,到被捕捉后终止生命的全过程中,实际上是一个传值过程,所以你可以根据需要,来合理的控制检测到"异常"的粒度。例如在例3中,如果你并不需要知道具体产生的是LowZeroException"异常",那么你可以使用"异常"的公共父类Exception来结合"异常"对象,即catch(Exception e){…}.同样在"异常"与方法关联的传递过程中,也可以根据需要控制关联"异常"的粒度,即throws后面跟上异常对象的父类名。 3."异常机制"中还有一种特殊情况――RuntimeException"异常类",这个"异常类"和它的所有子类都有一个特性,就是"异常 "对象一产生就被Java虚拟机直接处理掉,即在方法中出现throw 子句的地方便被虚拟机捕捉了。因此凡是抛出这种"运行时异常"的方法在被引用时,不需要有try…catch语句来处理"异常".=== 应该抛出异常的几种情况 ===在这些情况下,应该是要抛出异常的:    === 无法通过参数检查 ===        例如空值、参数取值范围等,因为参数的规则说明是作为方法签名的一部分发布的,调用者应该很清楚    调用这个方法所需要的参数的规格,但是调用者现在传入的错误的参数,说明调用者本身这时候很可能    就已经出现问题了。        对参数进行检查本身也是一个编写一个方法的良好习惯,这可以保证方法本身在一个相对比较稳定可知    道状态下工作,因此方法本身将更简单也更稳定。        总之,拒绝非法的参数,并抛出异常。=== 将可能导致以后的相关功能出错 ===        如果一个异常或者错误的参数将导致以后更多的异常或者以后的某个功能异常,那么不要延迟,立即抛    出异常。    例如有一个属性 Filename 用来保存将被保存的文件的名称,一个的方法 Save 将保存到 Filename 所    指定的文件名中去,显然在给 Filename 属性赋值时,如果给出一个包含非法文件名字符的字符串,那    么将导致 Save 方法中会有异常发生,不论 Save 方法是否对 Filename 再进行检查,这时候距离设定     Filename 的代码可能已经相当远了,因此在设定 Filename 的时候,就应该要进行这种有关的检查,    如果文件名不合法,立即抛出异常,而不要等到以后再来发现这个问题。        上述对参数进行检查其实很多也是这种情况,特别一些空值参数,将可能导致以后使用该参数的时候发    生空值异常。=== 不应该抛出异常的几种情况 ===    === 不影响程序继续运行 ===        如果一个错误并不影响程序逻辑的正确性,或者不影响此后代码的继续运行,那么可以不必抛出异常。        一个典型的例子例如,从集合中删除指定键值对应的元素,但指定的键值其实并不在集合中,这显然是    一个意外情况,但是,就此继续下去,以后的程序应该还可以继续有效的运行,因为即使在正常情况下    也不过是这个元素不再存在于集合中而已。返回一个整数值表示删除掉的元素的个数,如果没有找到给    定的元素,则返回0表示没有元素被删除。这个例子的另一种情形是,当前集合元素因为某种原因被锁住    不能删除元素,那么应该抛出一个异常表示删除失败,而不是返回0表示没有删除元素。
0 0
原创粉丝点击