切莫轻视JAVA异常处理

 
JAVA 异常处理是程序开发的一个重要内容,异常处理的好坏关系到系统的健壮性和稳定度。异常处理看起来只有几个常用 语句,故有些开发人员常常会对异常处理轻视和在使用上思路模糊。近期笔者在一个开发项目中就体验到轻视异常处理的惨痛教训,因为对异常没有处理好,后果是 严重影响系统稳定性。因此,笔者认为异常处理并不是表面看起来的那么简单。本文分享在此项目过程中对异常处理的一些看法。

   
. 什么是异常
   
JAVA 程序运行时,我们常常会出现一些非正常的现象,这种情况称为运行错误。根据其性质可以分为错误和异常。 JAVA 用面向对象的方法处理异常,首先 会建立类的层次。类 Throwable 位于这一类层次的最顶层,只有它的后代才可以作为一个异常被抛弃。类 Throwable 有两个直接子类: Error Exception

   
一般来说错误最常见的有程序进入死循环,内存泄漏等。这种情况,程序运行时本身无法解决,只能通过其他程序干预。 JAVA 对应的类为 Error 类。 Error 类对象由 JAVA 虚拟机生成并抛弃(通常 JAVA 程序不对这类异常进行处理)。

   
异常是程序执行时遇到的非正常情况或意外行为。一般以下这些情况都可以引发异常:代码或调用的代码(如共享库)中有错误,操作系统资源不可用,公共语言运 行库遇到意外情况(如无法验证代码)等等。常见的有数组下标越界,算法溢出(超出数值表达范围),除数为零,无效参数、内存溢出等。这种情况不像错误类那 样,程序运行时本身可以解决,由异常代码调整程序运行方向,使程序仍可继续运行直至正常结束。

    JAVA
对应的类为 Exception 类。 Exception 类对象是 JAVA 程序处理或抛弃的对象。它有各种不同的子类分别对应于不同类型的异常。 JAVA 编译器要求程序必须捕获或声明所有的非运行时异常,但对运行时异常可以不做处理。其中类 RuntimeException 代表运行时由 JAVA 拟机生成的异常,原因是编程错误。其它则为非运行时异常,原因是程序碰到了意外情况,如输入输出异常 IOException 等。

   
. 异常处理程序的功效
   
当在程序运行过程中发生的异常事件,这些异常事件的发生将阻止程序的正常运行。为了加强程序的稳定性,程序设计时,必须考虑到可能发生的异常事件并做出相应的处理。因此, 异常处理程序就是能够让系统在出现异常的情况下恢复过来的程序。

    JAVA
通过面向对象的程序来处理异常。在一个程序的运行过程中,如果发生了异常,则这个程序生成代表该异常的一个对象,并把它交给运行时系统,运行时系 统寻找相应的代码来处理这一异常。我们把生成异常对象并把它提交给运行时系统的过程称为抛出异常 (Throw) 。异常抛出后,运行时系统从生成对象的代码 开始,沿程序的调用栈逐层回溯查找,直到找到包含相应处理的程序,并把异常对象交给该程序为止,这个过程称为捕获异常 (Catch)

   
为了使异常处理更出色地发挥它的功效,程序员需要对所有可能发生的异常,预制各式各样的异常类和错误类。它们都是从抛出异常类 Throwable 继承而来的,它派生出两个类 Error Exception

   
Error 派生的子类命名为 XXXError ,其中词 XXX 是描述错误类型的词。由 Exception 派生的子类命名为 XXXException ,其中 XXX 是描述异常类型的词。 Error 类处理的是运行使系统发生的内部错误,是不可恢复的,唯一的办法是终止运行程序。因此,一般来说开发人员只要掌握 和处理好 Exception 类就可以了。对于运行时异常 RuntimeException ,我们没必要专门为它写一个异常控制器,因为它们是由于编程不严 谨而造成的逻辑错误。只要出现终止,它会自动得到处理。需要开发人员进行异常处理的是那些非运行期异常。

  
三、异常处理的两种思路
    JAVA
异常处理的一个好处就是允许我们在一个地方将精力集中在要解决的问题上,然后在另一个地方对待来自那个代码内部的错误。我们只需要在那个可能发生 异常的地方设置“监视区”,我们对此区域日夜监视着,通常它是一个语句块。同时我们还需要在另一个地方设置处理问题模块,如“异常处理模块”或者“异常控 制器”。这样可有效减少代码量,并将那些用于描述具体操作的代码与专门纠正异常的代码分隔开。一般情况下,会让用于读取、写入以及调试的代码会变得更富有 条理。

   
一般来说有两种思路处理异常。第一种将含有异常出口的程序直接放到 try 块中,然后由紧随其后的 catch 块捕捉。 JAVA try catch 语法来处 理异常,将关联有异常类的程序包含在 try{} 程序块中, catch(){} 关键字可以使用形参,用于和程序产生的异常对象结合。当调用某个程序时,引起 异常事件发生的条件成立,便会抛出异常,原来的程序流程将会在此程序处中断,然后 try 模块后紧跟的 catch 中的形参和此异常对象完成了结合,继而进入 catch 模块中运行。  

   
这里引用一个最简单的例子来说明:
int myMethod( int dt) ... {
int data = 0;
try ... {
    
int data = isLegal(dt);
}
catch (LowZeroException e)......... ... {
     System.
out .println(" 发生数据错误! ");
}
return data;
}
 
    第二种是不直接监听捕捉被引用程序的异常,而是将这个异常关联传递给引用程序,同时监听捕捉工作也相应向上传递。
 
 
. 解读五个异常处理语句的应用教训
  笔者结合本次项目教训谈谈 JAVA 异常处理的五个关键语句: try catch throw throws finally 。希望能与大家分享在本次项目开发遇到的问题和总结一些经验教训。
 
    4.1 Try catch 的教训
    try 语句用 {} 指定了一段代码,该段代码可能会抛弃一个或多个异常。 catch 语句的参数类似于程序的声明,包括一个异常类型和一个异常对象。异常类 型必须为 Throwable 类的子类,它指明了 catch 语句所处理的异常类型,异常对象则由运行时系统在 try 所指定的代码块中生成并被捕获,大括号中 包含对象的处理,其中可以调用对象的程序。
  
    JAVA 运行时系统从上到下分别对每个 catch 语句处理的异常类型进行检测,直到找到类型相匹配的 catch 语句为止。这里类型匹配指 catch 所处理 的异常类型与生成的异常对象的类型完全一致或者是它的父类。因此, catch 语句的排列顺序应该是从特殊到一般。也可以用一个 catch 语句处理多个异常 类型,这时它的异常类型参数应该是这多个异常类型的父类,程序设计中要根据具体的情况来选择 catch 语句的异常处理类型。
 
    异常被异常处理程序捕获和处理,异常处理程序紧接在 try 块后面,且用 catch 关键字标记,因此叫做“ catch 块”。如果一个程序使用了异常规范,我 们在调用它时必须使用 try-catch 结构来捕获和处理异常规范所指示的异常,否则编译程序会报错而不能通过编译。这正是 JAVA 的异常处理的杰出贡 献,它对可能发生的意外及早预防从而加强了代码的健壮性。
 
     在这次项目中得到一个教训是不要用一个 catch 语句捕获所有的异常和试图处理所有可能出现的异常。一个程序中可能会产生多种不同的异常,我们可以设置多 个异常抛出点来解决这个问题。异常对象从产生点产生后,到被捕捉后终止生命的全过程中,实际上是一个传值过程,所以我们需要根据实际需要来合理的控制检测 到异常的个数。 catch 语句表示我们预期会出现某种异常,而且希望能够处理该异常。我们建议在 catch 语句中应该尽可能指定具体的异常类型,必要时使 用多个 catch ,用于分别处理不同类的异常。实际上绝大多数异常都直接或间接从 JAVA.ang.Exception 派生。例如我们想要捕获一个最明显 的异常是 SQLException ,这是 JDBC 操作中常见的异常。另一个可能的异常是 IOException ,因为它要操作 OutputStreamWriter 。显然,在同一个 catch 块中处理这两种截然不同的异常是不合适的。如果用两个 catch 块分别捕获 SQLException IOException 就要好多了。这就是说, catch 语句应当尽量指定具体的异常类型,而不应该指定涵盖范围太广的 Exception 类。
 
    在此项目另一个教训是初级开发人员总喜欢把大量的代码放入单个 try 块,这个坏习惯使我们在测试和分析问题过程中花费了大量的时间。把大量的代码放入单个 try 块,然后再在 catch 语句中声明 Exception ,而不是分离各个可能出现异常的段落并分别捕获其异常。这种做法为分析程序抛出异常的原因带来 了困难,因为一大段代码中有太多的地方可能抛出 Exception 。程序的条理性和可阅读性也会变得非常差,因此我们需要尽量减小 try 块的体积。
 
    异常处理中还有一种特殊情况 ---RuntimeException 异常类,这个异常类和它的所有子类都有一个特性,就是异常对象一产生就被 JAVA 虚拟 机直接处理掉,即在程序中出现 throw 子句的地方便被虚拟机捕捉了。因此凡是抛出这种运行时异常的程序在被引用时,不需要用 try catch 语句来处理异常。
 
 
异常处理语句的一般格式是:
try ... {
// 可能产生异常的代码
}
catch ( 异常对象 e) ... {
//异常 e的处理语句 }
catch ( 异常对象 e1) ... {
//异常 e的处理语句 }
catch ( 异常对象 e2) ... {
//异常 e的处理语句
}
 
    4.2 解读 Throw throws 区别
    在使用异常规范的程序声明中,开发人员使用 throw 语句来抛出异常, throw 总是出现在函数体中。程序会在 throw 语句后立即终止,它后面的语句执行不到,然后在包含它的所有 try 块中从里向外寻找含有与其匹配的 catch 子句的 try 块。
throw 语句的格式为:
    throw new XXXException();
    由此可见, throw 语句抛出的是 XXX 类型的异常的对象(隐式的句柄)。而 catch 控制器捕获对象时要给出一个句柄 catch(XXXException e)
 
    如果一个 Java 程序遇到了它不能够处理的情况,那么它可以抛出一个异常:一个程序不仅告诉 Java 编译器它能返回什么值,还可以告诉编译器它有可能产生 什么错误。 JAVA 为了使开发人员准确地知道要编写什么代码来捕获所有潜在的异常,采用一种叫做 throws 的语法结构。它用来通知那些要调用程序的开发 人员,他们可能从自己的程序里抛出什么样的异常。这便是所谓的“异常规范”,它属于程序声明的一部分。
 
    throw 子句用来抛出异常,而 throws 子句用来指定异常。 throw 的操作数是 Throwable 所有派生类, Throwable 的直接子类是 Exception( 应捕获的问题,应进行处理 ) Error (重大系统问题, 一般不捕获)。抛出异常抛出点有 try{} 块、 , try{} 块某个深层嵌套的作用域、 try{} 块某个深层嵌套的程序中。简单说 throws 是指定 throw 抛出的异常。
 
    throws 总是出现在一个函数头中,用来标明该成员函数可能抛出的各种异常。对大多数 Exception 子类来说, JAVA 编译器会强迫你声明在一个成员函数中抛出的异常的类型。如果你想明确地抛出一个 RuntimeException ,你必须用 throws 语句来声明它的类 型。
    例如
    void f() throws tooBig tooSmall divZero { 程序体 }
    若使用下述代码:    
    void f() [ // …
    它意味着不会从程序里抛出异常。
 
    4.3. 巧妙应用 finally 使出口统一
    异常改变了程序正常的执行流程。这个道理虽然简单,却常常被人们忽视。我们在这次项目中就遇到这样的情况,就是无论一个异常是否发生,必须执行某些特定的 代码。比如文件已经打开,关闭文件是必须的。再如在程序用到了 Socket JDBC 连接之类的资源,即使遇到了异常,正常来说是也要正确释放占用的资 源。
 
    但是,在 try 所限定的代码中,当抛弃一个异常时,其后的代码不会被执行。在 catch 区中的代码在异常没有发生的情况下也不会被执行。为了无论异常是否 发生都要执行的代码,为此, JAVA 提供了一个简化这类操作的关键词 finally ,也就是无论 catch 语句的异常类型是否与所抛弃的异常的类型一致, finally 所指定的代码都要被执行。 Finally 保证在 try/catch/finally 块结束之前,执行清理任务的代码有机会执行,它提供了统 一的出口。
 
. 切莫轻视异常处理
    常常会有一些程序员习惯在编程时拖延或忘记异常处理程序的编写。因为轻视异常这一坏习惯是如此常见,它甚至已经影响到了 JAVA 本身的设计。代码捕获了异 常却不作任何处理,可以算得上 JAVA 编程中的杀手。从问题出现的频繁程度和祸害程度来看,如果你看到了出现异常的情况,可以百分之九十地肯定代码存在问 题。
 
    最好的方法是在进行系统设计就把异常处理融合在系统中,若系统一旦实现,就很难添加异常处理功能。因此从项目一开始就应该着手进行异常处理,必须投入大精力把异常处理的策略融合到软件产品中。
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值