文章导读
【编写Java程序时,经常会遇到错误。一类错误是语法错误或资源链接错误,此类错误一般在编译过程中就可以发现;另一类是在程序运行过程中出现的错误,该类错误被称为程序异常,由于程序异常严重影响了程序运行的稳定性。因此,程序人员必须要对此类错误进行预防和处理,对异常处理的过程即异常处理。本文介绍了如何处理程序异常的方法和技术,并给出了主要的Java异常处理类,在程序出现异常时,可以利用Java提供的异常处理类捕获异常并对异常进行处理。】
本文分成三个小节介绍Java的异常处理。第一小节主要认识什么是程序异常,以及出现异常后,程序该如何进行处理;第二小节主要介绍Java提供的异常处理类,这些异常处理类可以帮助程序员捕获异常;第三小节主要介绍try—catch语句。
第一小节 在Java中如何处理程序出现的异常
在工作和现实生活中,会碰到很多异常处理的事例。例如,在软件项目开发过程中,团队成员的突然离职、客户对需求的变更、开发进度拖延等情况的发生,都会导致项目开发过程出现异常,并需要项目经理及时处理这些异常情况。
异常不可避免,但可以提前对异常做出预测和预处理,预测什么情况下会出现异常,出现异常后如何进行处理。
例如,前面谈到的软件项目开发过程出现的异常,可以在项目管理计划书中针对团队成员离职、需求变更、进度拖延出现的异常,制定详尽的应对计划,该应对计划就是异常处理。
对于程序而言,内存溢出、数组越界、除零操作、对象没有实例化就使用、变量无赋值就使用等操作都会引发程序异常。因此,程序员在编写代码时,需要对上面的情况做出预测,并添加出现异常时的处理语句,以提高程序的稳定性。
在不支持异常处理的程序设计语言中,程序员为了检查可能发生的异常情况,需要使用很多的if—else语句,这就要求程序员非常清楚地知道是什么导致了异常的产生,以及异常的确切含义。
无异常处理机制的代码如下:
//除法计算
int div(Intege a, Intege b)
{
int ret = -1;
//预测传入参数a为空的异常处理代码
if( a == null )
{
System.out.println("传入的参数a为空");
return ret;
}
//预测传入参数b为空的异常处理代码
else if( b == null )
{
System.out.println("传入的参数b为空");
return ret;
}
//预测传入参数b不能为0的异常处理代码
else if( b == 0 )
{
System.out.println("传入的参数b为0,b做为除数不能为0");
return ret;
}
ret = a / b;
return ret;
}有异常处理机制的语言,没有必要去编写上述的这些if—else语句。在默认的情况下,异常会输出一个错误消息,并中止程序的执行。为了更好地处理异常情况,程序员通常会在程序中定义异常处理语句来捕获和处理异常。Java语言提供了try—catch语句来捕获和处理异常,try—catch语句后面会详细讲解。
使用try—catch语句对上面的例子代码提供异常处理的支持,修改后的代码如下:
//除法计算
public int div(Intege a, Intege b)
{
int ret = -1;
//try语句块用于捕获异常
try {
ret = a / b;
}catch(Exception e1){
//catch语句块用于处理异常
System.out.println(e1.getMessage());
}
return ret;
}采用try—catch语句不仅可以使代码变得更简洁,而且能为程序调试提供很大的方便,从而达到提高程序稳定性的目的。
第二小节 认识Java的异常及异常处理类
前面介绍了Java的异常处理,对异常有了大概了解,现在再从编码的角度来理解异常。试运行如下代码:
public class ShowException {
public static void main(String[] args) {
int a=30,b=0,c;
//零做为除数,程序抛出异常
c= a / b;
}
}上面的代码中,b为零值,当执行c= a / b语句时,程序抛出异常,控制台会显示如下图所示的内容:

图 1 除数为0时,抛出异常
当然如果程序员对变量b做了预防为0的判断,控制台将不会显示图中所示内容,这就是异常,也就是程序运行过程中出现的错误或不正常的情况。发行异常就需要处理,Java语言本身也提供了处理异常的机制。
Java提供了一个Throwable类,Throwable类是Java语言中所有处理错误或异常的超类,只要当对象是此类或其子类之一的实例时,才能通过Java虚拟机或者java的throw语句抛出异常。类似的,只有此类或其子类之一才可以是catch子句中的参数类型。它的两个子类的实例Error和Exception通常用于指示发生了异常情况。这些实例是在异常情况的上下文中创建的,因此包含了相关的程序运行信息。Throwable类及其子类的结构如下图所示:

图 2 Throwable类及其子类结构图
Throwable类充当所有对象的父类,可以使用异常处理机制将这些对象的异常抛出并捕获。在Throwable类中定义方法来检索与异常相关的错误信息,并打印显示异常发生的栈跟踪信息。它有Error和Exception两个基本子类。
● 错误(Error):JVM系统内部错误,资源耗尽等严重情况。
● 异常(Exception):其它因编程错误或偶然的外在因素导致的一般性问题。例如,除数为零、数组越界、空指针访问、读取不存在的文件等。
当发生Error时,程序员根本无能为力,只能让程序终止。例如内存溢出,不可能指望程序能处理这样的情况。而对于Exception,则有补救或控制的可能,程序员也可以预先防范。异常处理主要是针对Exception进行,Exception的API说明如下图所示:

图3 Exception类的API说明
图中包含了众多Exception类的已知子类,也不必担心这么多子类记不住,在实际使用时查阅异常类的文档即可。
下面列出常见的异常类:
■ Exception:异常层次结构的根类
■ RuntimeException:Java.lang异常的基类
■ ArithmeticException:算术错误异常类,如零作为除数
■ ArrayIndexOutOfBoundsException:数组越界异常处理类
■ NullPointerException:尝试访问NULL 对象或成员
■ ClassNotFoundException:不能加载所需的类
■ IOException:I/O异常的根类
第三小节 使用try和catch捕获异常
Java程序在执行过程中如果出现异常,会自动生成一个异常对象,该异常对象将被自动提交给JVM,当JVM接收到异常对象时,会寻找能处理这一异常的代码,并把当前异常对象交给其处理,这一过程称为捕获(catch)异常。如果JVM找不到可以捕获异常的方法,则运行时系统将终止,相应的Java程序也将退出。
在java中添加捕获异常代码时,需要把认为可能会出现异常的代码包括在try语句块内,处理异常的代码包括在catch语句内。在程序执行时,如果try语句内的代码出现错误,try会创建异常对象并抛出,catch捕获异常对象,则catch语句块内的代码将会执行,这样就可以处理异常错误了。
使用try和catch的语法规则如下:
try {
可能发生异常的代码
}
catch(异常类型 ex) {
对异常进行处理的代码
}
下面通过示例演示try和catch的用法,代码如下:
package com.milihu.trydemo;
public class TryTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
number = Integer.parseInt(args[0]);
//如果存在异常,下面这行代码是不会输出的
System.out.println("程序没有发生异常");
} catch(Exception e) {
System.out.println("非法的数字");
}
System.out.println("您输入的数字为:" + number);
}
}上面的代码描述了从控制台获取用户输入的参数,如果将该参数转换为数值成功,即输出转换后的数;如果转换有异常,就说明用户输入的是非法的数字。代码非常简单,主要是通过这段代码说明两个问题:一是程序没有异常时,catch的语句是否会被执行;二是程序有异常时,最后的语句还会被执行吗?
(1)输入一个未能转化为数值的参数
新建一个Java项目,输入上面的代码。在菜单栏中选择Run as → Configurations命令,在弹出的Run对话框中选择Arguments标签,在Program arguments文本框中输入“hello”,然后单击Run按钮,如下图所示:

图4 设置程序运行参数
程序运行后,控制台输出结果如下图所示:

图 5 参数不为数字时的输出结果
(2)输入一个能转化为数值的参数
重复上面的操作,在Program arguments文本框中输入“2”,然后单击Run按钮,控制台输出的结果如下图:

图 13?6 当参数为数值2时的输出结果
通过上述两种方式的比较可以看出,当有异常发生时,要执行catch括号中的语句。并且catch之后的语句也会执行;没有异常发生时,不会执行catch括号中的语句。
前面介绍的是处理单个异常,有时代码可能会引发多个异常,在这种情况下,可以定义两个或更多的catch子句,每个子句捕获一种类型的异常。
多重catch语句的语法规则如下:
try {
………. //代码段
………. //产生异常
}
catch(异常类型1 ex) {
………. //对异常1进行处理代码段
} catch(异常类型2 ex) {
………. //对异常2进行处理代码段
}
} catch(异常类型n ex) {
………. //对异常n进行处理代码段
}
下面通过示例演示多重catch的用法,代码如下:
class MultiCatch {
public static void main(String args[]) {
try {
int a = args.length;
System.out.println("a = " + a);
int b = 42 / a;
int c[] = { 1 };
c[10] = 99;
} catch(ArithmeticException e) {
System.out.println("Divide by 0: " + e);
} catch(ArrayIndexOutOfBoundsException e) {
System.out.println("Array out of bounds: " + e);
}
System.out.println("After try/catch blocks.");
}
}程序在没有命令行输入参数条件下运行导致发生被零除异常,因为a为0。如果输入一个命令行参数,或把a设成大于零的数值,不会出现被零除的异常。但是它将导致ArrayIndexOutOf BoundsException异常,因为整型数组c的长度为1,而程序试图给c[10]赋值。
从上面的例子可以看出,一段代码中可能会产生多种不同的异常,可以设置多个异常抛出点来解决这个问题。
文章小结
1、异常处理就是提前预测程序可能发生的所有异常情况,并通过Java提供的异常处理类捕获异常。当出现异常后,程序执行的流程发生改变,程序的控制权转移到异常处理代码。
2、异常处理机制可以从两个方面来描述,当一个Java程序违反了Java语义规范时,JVM虚拟机就会抛出一个异常。例如数组下标越界,会引发IndexOutOfBoundsException异常,访问null的对象时会引发NullPointerException异常。另一种情况就是JAVA允许程序员扩展这种语义检查,程序员可以创建自己的异常,并自由选择在何时用throw关键字引发异常。所有的异常都是java.lang.Thowable的子类。
3、异常对象从被抛出到捕捉,实际上是一个传值过程,所以可以根据程序需要合理地控制检测到异常的粒度。如果并不需要知道具体产生的是什么异常,那么可以使用异常的公共父类Exception来处理异常对象,即catch(Exception e){….} 。
思考与练习
编写一个ExceptionTest类,在main方法中使用try—catch语句捕获可能出现的异常并对异常进行处理。