Java异常处理机制应用研究❋

2014-08-07 12:09欧阳宏基
微处理机 2014年6期
关键词:编译器语句代码

欧阳宏基,葛 萌

(咸阳师范学院信息工程学院,咸阳712000)

Java异常处理机制应用研究❋

欧阳宏基,葛 萌

(咸阳师范学院信息工程学院,咸阳712000)

异常处理机制是Java程序设计中的一个重要方面,正确使用异常处理的策略和方法,能够确保Java程序结构的清晰性、易读性和健壮性。分析了Java异常的体系结构,阐述了异常分类与处理机制,提出了异常处理的一般原则和一种异常处理框架,并结合实际应用对该框架进行了详细描述。

Java异常处理机制;异常处理原则;异常处理框架

1 引 言

异常(又称为例外)是程序编译或运行时发生的可预料或不可预料的非正常现象,可能会导致程序中断或错误结果[1]。异常是不可避免的,出现了什么样的异常?由谁来如何处理异常?如何从异常中恢复?这些问题是任何一门编程语言都要解决的。传统面向过程的程序语言(例如C语言)通常根据程序返回的某个特殊值或标记,并且假定接收者会检查该返回值或标记,以此来判断错误是否发生[2]。这种处理方式会在程序的许多地方逐一检查某个特定的错误并加以处理,导致正常的业务流程和错误处理代码紧密耦合,不利于代码的阅读和维护。Java语言提供了一整套的、高效的、包括异常抛出、捕获和处理的机制用于识别和处理异常,并且由Java编译器强制执行,将描述业务逻辑的代码与处理异常的代码分离开来,从而使代码的可读性、撰写、调试和维护都大大提高。

2 Java异常体系结构

任何中断程序正常流程的因素都被认为是异常,由于Java是纯面向对象的,所以把异常当作对象来处理。JDK API中根据访问不同资源(例如内存、文件、数据库等)定义了许多具体异常类,同时允许开发人员根据项目需要自行定义异常类用来描述实际异常信息。层次结构如图1所示。

Throwable是所有异常和错误的父类,它主要包含三个方面的内容:①线程创建时执行堆栈的快照。②用以描述异常或错误出现位置的消息字符串。③异常或错误产生的原因。Throwable有两个直接子类:Error和Exception,分别表示错误和异常。其中异常Exception又包括两大类:运行时异常(RuntimeException)和非运行时异常。运行时异常又称为编译器不检查的异常(Unchecked Exception),非运行时异常又称为编译器检查的异常(Checked Exception)。下面将详细描述这些异常之间的区别与联系:

图1 Java异常体系结构

2.1 Error与Exception

Error类层次结构描述了Java运行时系统的内部错误和资源耗尽错误,例如OutOfMemoryError(内存溢出错误)、NoClassDefFoundError(类定义找不到错误)等。如果这些错误发生(一般情况很少发生),Java虚拟机(JVM)不会检查Error是否被处理,除了通知给用户并且会尽力使程序安全的终止外,程序本身是无法处理这些错误的。Exception分两大类:运行时异常和非运行时异常。开发人员在代码中应当尽可能去处理这些异常,从而保证程序正确执行。

2.2 运行时异常和非运行时异常

各种具体的运行时异常都是RuntimeException类及其子类对象,例如ClassCastException(强制类型转换异常)、IndexOutOfBoundsException(下标越界异常)等。因为这类异常只有在程序运行阶段才能体现出来,所以Java编译器在编译阶段对代码是否处理了该类型异常不做检查,编译能正确通过。该类型异常一般是由程序逻辑错误引起的,所以应从逻辑角度尽可能避免这类异常的发生。各种具体的非运行时异常都是RuntimeException以外的异常,直接从Exception继承而来,例如IOException(输入输出异常)、SQLException(数据库操作异常)等。这类异常在代码中必须进行处理,否则编译不会通过,所以又称为编译器检查异常。

3 Java异常处理机制

异常处理是指当异常发生后,程序能够转向相关的异常处理代码中并执行尝试性修复处理,然后根据修复处理的结果决定程序走向,使应用程序能够正常运行、或降级运行或安全地终止应用程序的执行,以提高应用系统的可靠性[3]。Java异常处理机制通过提供5个关键字用来完成对异常的抛出、捕获和处理这三个过程,分别是:try、catch、finally、throw、throws。前三个关键字可分别包含独立的代码段依次用来抛出异常、匹配并捕获异常和处理异常;后两个关键字用来将当前方法所产生的异常声明抛出,将异常的捕获和处理操作交给当前方法的调用者。其中try、catch和finally这三个关键字所包含的代码段的执行情况如图2所示:其中左边表示没有异常产生时的执行流程,右边表示异常发生时的执行流程。

图2 try catch finally代码段的执行情况

(1)try代码段:包含在try中的代码段可能有多条语句会产生异常。但程序的一次执行过程中如果产生异常,只可能是这些异常中的某一个,该异常对象由Java运行时系统生成并抛出,try中产生异常语句之后的语句都不会被执行;如果这次执行过程中没有产生异常,那么try中所有的语句都会被执行。

(2)catch代码段:捕获try中抛出的异常并在其代码段中做相应处理,catch语句带一个Throwable类型的参数,表示可捕获异常的类型。一般情况下catch代码段的数量由try中所抛出的异常个数决定。当try中代码产生的异常被抛出后,catch代码段按照从上到下的顺序(如果异常类型有父子关系,那么子异常所在的catch代码段位于父异常所在catch代码段的上方)将异常类型与自己参数所指向的异常类型进行匹配,若匹配成功表示异常被捕获,程序转而执行当前catch中的代码,后面所有的catch代码段都不会被执行;如果匹配不成功,交给下一个catch进行匹配;如果所有catch都不匹配,表示当前方法不具备处理该异常的能力,对于这种情况如果是一个非运行时异常,为了编译器通过,必须使用throws关键字声明抛出。

(3)finally代码段:该代码段不是必须有的,但如果有,一定紧跟在最后一个catch代码段后面,作为异常处理机制的统一出口。无论try中是否产生异常,finally中的代码总在当前方法返回之前无条件执行(除非已经执行了要终止程序的System.exit()方法)。

(4)throw关键字用来在方法体内部创建Throwable类型的异常对象并将其抛出,如果是非运行时异常,还必须结合throws关键字在方法头部声明抛出该异常类型,表明当前方法不具备处理该异常的能力,将异常的处理任务延迟到当前方法的调用者。当前方法的调用者必须检查、处理或者继续抛出被调用方法抛出的异常。如果所有方法都层层上抛获取的异常,最终会在main方法中寻找对应的catch代码段。如果main中也没有对异常进行捕获,那么JVM将通过控制台打印该异常消息和堆栈信息,同时程序也会终止。

(5)throws关键字用来在方法头部声明方法可能会抛出的某些异常。仅当抛出了非运行时异常,该方法的调用者才必须处理或者重新抛出该异常。如果方法的调用者无法处理该异常,应该继续抛出而不是在catch中向控制台打印异常发生时的堆栈信息,虽然这样处理对程序调试有帮助,但当程序交付给客户运行后,printStackTrace这样的代码就不具备处理异常的意义了。

4 异常处理的一般原则[4-7]

4.1 尽可能较早处理异常

一般情况下,try-catch语句不会对应用的性能造成很大影响。仅仅当异常发生时,Java虚拟机需要执行额外的操作来定位处理异常的代码段,从而会对系统性能产生负面影响。如果抛出异常的代码段和捕获异常的代码段在同一方法中,此种情况对性能的影响最小;如果Java虚拟机必须搜索方法调用栈来寻找异常处理的代码段,对性能的影响就比较大,尤其当异常处理代码段位于方法调用栈的底部时。因此,不应该使用异常处理机制来控制程序的正常流程,而应该确保仅仅在程序中可能出现异常的地方使用try-catch语句。而且应该使异常处理代码位于适当的层次,如果当前方法具备处理某种异常的能力,就尽量处理而不是把能处理的异常抛给方法的调用者。

4.2 对异常进行转译

异常转译就是将一种异常转换为另一种新的异常,使得新的异常更能准确描述程序产生异常的原因。因为任何形式的异常和错误都是Throwable的子类,而且任何一个异常类都包含接收一个Throwable类型参数的构造方法,这样就为异常转译提供了支持。异常转译通常有三种情况,如图3所示。

图3 异常转译

(1)表示将错误转译为编译器检查的异常并继续抛出。这样做的目的是为了最大限度避免因错误发生而导致的系统挂起。例如SpringWEB框架中,org.springframework.web.servlet.DispatcherServlet的doDispatch()方法中,将捕获的错误转译为一个NestedServletException异常。

(2)表示将编译器检查的异常转译为Runtime-Exception。可能因为当前方法为了更好的描述业务逻辑而不方便对异常进行有效处理,转译为RuntimeException交给上层调用者来处理。例如DAO是Java EE中数据持久层常用的一种设计模式,如果采用JDBC来访问数据库就必须对SQLException这个受检查的异常进行处理。为了将这个异常交给上层调用者,将SQLException转译为另外一个新的异常-DAOException(开发人员自己定义的),示例代码如下所示:

e是一个SQLException类型的异常对象。

(3)表示将错误转译为RuntimeException。这样做也是为了将错误信息交给上层调用者进行统一处理,不过此种情况在实际开发中较少使用。

4.3 在catch中指定具体的异常类型

不要使用catch(Exception ex)这样的语句来捕获异常,虽然这样能编译通过而且不用仔细分析try代码段中异类的类型。根据图1得知所有的异常都直接或间接从java.lang.Exception继承而来,根据Java上泛型原理,任何一个异常都会和catch(Exception ex)这样的代码匹配,导致子类异常对象“丧失”了被抛出时的上下文,那么在catch代码段中就无法根据不同异常类型做出不同的处理,也就违背了异常处理的初衷。所以catch语句应当尽量指定具体的异常类型。

4.4 严格控制try代码段的容量

不要把大量的语句都放在某个单一的try代码段中,因为一大段语句中可能会有不同地方抛出不同的异常。应该仔细分析一大块代码中哪些行代码会抛出什么样的异常,结合实际程序逻辑,将这些代码拆分到不同的try代码段中并结合具体的异常分别进行处理。虽然这样做增加了try的数量,但是减少了try代码段中的代码行数,并且使得异常类型容易分析、代码易于阅读。例如采用JDBC进行数据库编程通常都要执行①加载数据库驱动、②创建Connection对象、③创建PreparedStatemnt或Statement对象执行SQL操作、④释放连接等资源这4个步骤。由于步骤①可能会产生ClassNotFoundException,步骤②③④可能会产生SQLException,所以应该将①放在一个try代码段中,②③④放在另外一个try代码段中分别进行捕获和处理。

4.5 合理使用finally代码段

虽然Java语言提供了垃圾回收机制来自动释放不再被引用的对象空间,但是如果程序中用到了内存以外的资源,例如IO流、Socket网络连接以及JDBC数据库访问之类的操作,即使发生了异常,也需要通知当前操作系统正确释放分配给JVM的底层资源。因此,必须把释放资源之类的代码(基本都是调用相应的close方法)放到finally代码段中,从而确保无论异常发生与否,释放资源之类的代码总是会被执行。如果finally中还有抛出异常的语句,那么就必须通过try-catch代码段对相应异常进行捕获并处理,而不能再声明抛出了。

5 一个高效合理的异常处理框架

对于一个应用系统来说,异常信息不但要让开发人员看到而且也要让用户看到。对用户而言,异常信息要简单明了、便于理解;对开发人员而言,异常要便于处理。由于目前Java应用的开发都是采用分层原理,每一层都完成特定的功能,在每一层都有可能产生异常,如果在每一层都处理异常会加重程序员的负担而且影响代码的易读性,因此需要选择一个合适的位置对异常进行集中处理。

由于Java支持自定义异常,所以创建自定义异常-AppRuntimeException继承自RuntimeException,如图4所示。根据4.2节原理其他异常都能转译为AppRuntimeException。在AppRuntimeException下层存在着各种具体的异常和错误,可以将错误和非运行时异常都向AppRuntimeException转译,这样做有两大优点:①最大限度地避免因错误发生而导致的系统崩溃;②使程序代码更加简洁,有利于错误和异常的统一处理。AppRuntimeException异常的下层可根据Java EE分层开发的原理再自行定义不同的子类异常,例如持久化层的AppDAOException异常。由于持久层的实现技术有多种,例如JDBC、Hibernate、TopLink等。这些不同技术都有最“原始”的异常,例如SQLException、HibernateException等,考虑到松散耦合和可移植性,这些具体异常要向AppDAOException转译,在转译过程中可通过print-StackTrace打印具体异常信息,方便开发人员调试。通过异常的层层上抛,如果是Web项目,可以在Servlet中或者Struts框架的Action中对AppRuntimeException进行处理[8],并调用相应的错误页面将异常信息简明的显示给用户;如果是桌面应用,可以在窗体类(Frame以及子类)中对异常进行集中处理,并调用相应的Dialog对象框将异常信息显示给用户。

图4 异常处理框架

6 结束语

异常处理机制是Java语言进行软件开发和测试的一个重要方面。阐述了Java的异常体系结构,详细描述了Java的异常处理机制,包括try、catch、finally、throw和throws关键字的用法和意义。总结了一些异常处理时应遵循的原则,并结合实际项目开发需求,提出了一种异常处理框架。综合运用这些策略和方法,可以使开发人员编写出更加简洁、高效的Java代码。

[1]杜春涛.Java 6基础教程[M].北京:清华大学出版社,2011:190-192.

[2][美]Bruce Eckel著.Java编程思想(第二版)[M].侯捷,译.北京:机械工业出版社,2002:382-383.

[3]王新雨,须文波,柴志雷.Java虚拟机中异常机制实时性的研究及实现[J].计算机工程与应用,2008,44(34):84-86.

[4]陈红跃,张宏军,陈刚.Java异常处理策略研究[J].计算机技术与发展,2012,22(7):9-12.

[5]赵智.Java异常处理机制使用经验与技巧[J].百色学院学报,2008,21(3):102-106.

[6]韩瑞峰.Java异常处理机制及应用研究[J].忻州师范学院学报,2012,28(2):25-27.

[7]杨厚群,陈静.Java异常处理机制的研究[J].计算机科学,2007,34(3):286-289.

[8]刘淑华.J2EE项目中一种新的错误处理方法[J].计算机应用与软件,2013,30(7):143-145.

Application Research of Java Exception Hand ling Mechanism

OUYANG Hong-ji,GE Meng
(Information Engineering College,Xianyang Normal University,Xianyang 712000,China)

The exception handling mechanism is an important aspect in Java programming,the strategies and methods for proper use of exception handling can ensure the clarity,legibility and robustness of Java Program Structure.The architecture of Java exceptions is analyzed,the exception handling mechanism of classification is described,and its general principles and framework are presented.The framework combined with the practical application is described in details.

Java Exception Handling Mechanism;Exception Handling Principles;Exception Handling Framework

10.3969/j.issn.1002-2279.2014.06.019

TP311

:A

:1002-2279(2014)06-0066-04

咸阳师范学院专项科研计划项目(12XSYK070)

欧阳宏基(1982-),男,陕西宝鸡人,讲师,硕士研究生,主研方向:软件工程、Java EE应用。

2014-04-01

猜你喜欢
编译器语句代码
重点:语句衔接
基于相异编译器的安全计算机平台交叉编译环境设计
创世代码
创世代码
创世代码
创世代码
Microchip为MPLAB XC系列专业版编译器推出低成本可续订包月许可证
通用NC代码编译器的设计与实现
如何搞定语句衔接题
作文语句实录