12处理异常.docx
《12处理异常.docx》由会员分享,可在线阅读,更多相关《12处理异常.docx(17页珍藏版)》请在冰豆网上搜索。
12处理异常
第四篇高级技术
第12章处理异常
我们知道,程序错误分为3种,即,编译错误、运行时错误和逻辑错误。
编译错误是因为程序没有遵循语法规则,编译程序能发现错误的原因和位置;运行时错误是因为程序执行时,运行环境发现了不能执行的操作;逻辑错误是因为程序没有按照预期的方案执行。
异常就是指程序运行时发生的错误。
而异常处理就是对这些错误进行处理和控制。
12.1异常现象
当程序运行发生错误时,系统抛出异常对象,并中断程序的正常控制流程。
如果程序中没有专门的代码来捕捉和处理异常对象,程序就可能非正常结束,并引起严重问题。
程序运行时出现异常的原因有很多,比如用户输入了一个无效的数据、程序试图打开一个不存在的文件、网络连接可能已经挂起、程序试图访问一个越界的数组元素等。
【例12.1】本例演示数组下标越界异常和除数为0异常。
该程序的作用是从程序执行的命令行中获取2个参数值,即args[0]和args[1],然后计算args[0]/args[1]的值。
程序清单12-1ArrayException.java
publicclassArrayException
{publicstaticvoidmain(Stringargs[])
{try{inta=Integer.parseInt(args[0]);//获取命令行的第一个参数
intb=Integer.parseInt(args[1]);//获取命令行的第二个参数
intc=a/b;//第一个参数做被除数,第二个参数做除数。
System.out.print(a+"/"+b+"="+c);
}
catch(ArrayIndexOutOfBoundsExceptione)
{e.printStackTrace();System.out.println("缺少命令行参数");}
catch(ArithmeticExceptionE)
{E.printStackTrace();System.out.println("除数为0");}
}
}
程序运行时,正常输出了循环的前4句,但在试图输出a[4]时,Java抛出一个异常对象。
系统报告了异常对象的类型(java.1ang.ArraylndexOutOfBoundsException:
数组越界异常类)及异常发生所在的方法,同时终止程序运行。
12.2java异常类
异常对象封装了运行时发生的错误的类型及错误发生时程序的状态。
Java语言对程序中可能出现的异常作归类,并对这些异常类进行了预定义。
在图形程序设计中,事件可以忽略,但是异常是不能忽略的。
当异常出现时,如果没有代码对异常进行处理,程序就会终止。
1.Throwable类
java.lang包中的Throwable类是所有异常类的父类。
异常类的继承关系如图12-1所示。
Throwable类的方法介绍如下:
●publicstringgetMessage():
返回异常发生时的详细信息。
●publicstringtoString():
返回发生异常时的简要描述。
●publicstringgetLocalizedMessage():
返回异常对象的本地化信息。
使用Throwable的子类覆盖这个方法,可以生成本地化信息,如果子类没有覆盖该方法,则该方法返回的信息与getMessage()返回的结果相同。
●publicvoidprintStackTrace():
在控制台上打印Throwable对象封装的异常信息。
注意:
一个异常父类可以派生多个异常子类。
如果一个catch子句捕获了一个父类的异常对象,它就能捕获该父类所有子类生成的异常对象。
图12-1异常类层次结构
2.错误类型与异常类型
程序中经常出现三类错误,它们是:
系统错误、异常、编程错误。
下面介绍处理这三种错误的异常类。
(1)Error类
系统内部发生的错误由Error类描述。
因为该异常是系统程序产生的,因此,程序无法处理这种异常,如果出现了这种错误,用户只能终止程序。
该异常由java虚拟机抛出。
(2)Exception类
外部环境和应用程序引起的错误由Exception类描述。
可以在程序中捕获和处理这种错误。
Exception的子类说明如下:
●IOException。
该类描述了输入/输出操作引起的异常。
●AWTException。
该类描述了图形界面中的组件引起的异常。
●ClassNotFoundException。
程序企图使用一个不存在的类。
●CloneNotSupportedException。
企图克隆一个对象,而定义该对象的类没有实现接口Cloneable。
(3)RuntimeException类
编程中的出现的错误由RuntimeException类描述。
如不合适的数据转换,访问一个越界数组元素等等,都会出现这种异常。
该种异常由java虚拟机抛出。
RuntimeException类的子类说明如下:
●ArithmeticException。
一个整数除以0时抛出该异常。
●NullPointerException。
要访问的变量没有引用任何对象时,抛出该异常。
●IndexOutofBoundsException。
要访问的数组元素的下标超出范围时,抛出该异常。
●IllegalArgumentException。
将无效或不合适的参数传递给方法。
12.3异常处理方法
Java对异常的处理涉及三种操作:
声明异常、抛出异常和处理异常。
Java程序处理异常的方式是:
将声明异常类型和抛出异常的操作封装在一个方法中,将捕获异常和处理异常封装在另一个方法中。
12.3.1声明和抛出异常
声明异常就是告诉方法的调用者,当调用该方法时可能出现的错误。
抛出异常是指程序检查到一个错误后,创建一个合适的异常对象并抛出它。
因为任何代码执行时都有可能发生系统错误(Error)和运行时错误(RuntimeException),因此在方法声明时,Java系统不要求显式地声明Error和RuntimeException两种异常类(系统隐含地声明了这两种异常类),但是,可以在方法体中抛出这两种异常对象。
需要注意的是,如果要在方法体中抛出其他异常,必须在方法头中声明这种异常类。
1.声明异常类的格式
在方法头中使用关键字throws可以声明异常类。
例如:
publicvoidmyMethod()throwsIOException//这里声明的异常类是:
IOException
关键字throws指出方法myMethod在执行时有可能抛出IOException异常(对象)。
如果方法执行时可能抛出多种异常,可以在关键字throws后添加多种异常类的列表,异常类之间用逗号分隔。
例如:
methodDeclarationthrowsException1,Exception2,…,ExceptionN
2.抛出异常对象的格式
在方法头中声明了异常类后,就应该在方法体中抛出一个与方法头中声名的异常类相一致的异常对象。
例如:
ThrownewTheException();//抛出异常(对象)
或
TheExceptionex=newTheException();
Throwex;//抛出异常对象ex
注意:
声明异常类的关键字是throws,抛出异常对象的关键字是throw。
在方法体中,只能抛出方法头中声明过的异常及Error、RuntimeException异常和它们的子类的实例。
例如,如果没有在方法声明中声明IOException,那么在方法体中就不能抛出它;但是,即使方法头中没有声明RuntimeException或它的子类,在方法体中可以抛出它们的实例,因为,java系统隐含地认为在每个方法头中声明了RuntimeException或它的子类。
3.声明和抛出异常的方法
在方法中声明和抛出异常的通用格式如图12-2所示。
图12-2在方法中声明和抛出异常
在上图中,在方法体中抛出的异常对象必须在方法头中声明过的异常类。
12.3.2捕获和处理异常
在java程序中,实现捕获和处理异常是通过try-catch语句块实现的。
try子句实现捕获异常的功能,catch子句实现处理异常的功能。
1.try-catch语句格式
try-catch语句由一个try子句和多个catch子句组成。
我们把有可能抛出异常的语句放在try子句中。
由多个catch子句对try子句中抛出的异常进行处理,因此,catch子句的作用是处理异常。
try-catch语句格式如下:
try
{…
Statements;//把所有可能抛出异常的语句安排在此
}
catch(Exception1ex)//检测Exception1异常
{
Handlerforexception1;//Exception1异常在此处理异常
}
catch(Exception2ex)/检测Exception2异常
{
Handlerforexception2;//Exception2异常在此处理异常
}
……
catch(ExceptionNex)//检测ExceptionN异常
{
HandlerforexceptionN;//ExceptionN异常在此处理异常
}
我们把一个catch子句称为异常处理器。
下面就是一个异常处理器的示例。
catch(ExceptionNex)//检测ExceptionN异常
{
HandlerforexceptionN;//处理ExceptionN异常
}
2.try-catch语句执行原理
try-catch语句是从上往下执行的:
(1)当执行try子句时,如果没有抛出异常,则跳过所有的catch子句,结束整个
try-catch语句的执行。
(2)如果try子句中的某条语句抛出一个异常,系统就会跳过try子句中的其余语句,开始为该异常搜索异常处理器。
即在下面的多个catch子句中,按照从上到下的顺序,寻找与抛出的异常类型一致的catch子句,若某个catch子句的异常类型与已抛出的异常匹配,就会执行该catch子句中的代码(即处理异常);如果被抛出的异常与任何一个catch子句中的异常类型都不匹配,Java就会退出try-catch语句所在的方法,并将异常传递给该方法的调用语句,继续重复寻找异常处理器。
如果在方法调用链中始终没有找到异常处理器,程序就会中止,并在控制台上打印出错误信息。
注意:
如果一个方法定义时声明了异常,则调用该种方法的语句应该放在try子句中,以便捕捉方法中抛出的异常。
任一catch子句执行完后,系统就不再执行其他的catch子句,标志着整个try-catch语句执行完毕。
3.方法调用链
捕获和处理异常的过程,就是方法链调用的过程。
下面以4个方法为例说明方法链的调用过程。
假设有main()、method1、method2、method3四个方法,当程序从main()方法开始执行后,这四个方法的调用顺序构成一个方法调用链。
它们之间的调用关系如图12-3所示。
图12-34个方法构成的调用链
在上图中,我们把声明异常和抛出异常封装在同一个方法中,把处理异常放在另3个方法中。
(1)异常处理器
处理Exception1异常的异常处理器,如图12-4所示。
处理Exception2异常的异常处理器,如图12-5所示。
处理Exception3异常的异常处理器,如图12-6所示。
图12-4处理器一
图12-5处理器二
图12-6处理器三
catch子句由2部分组成:
参数表和处理异常的多条语句组成。
参数表用来检查异常的,如(Exception1ex1)、(Exception2ex2)、(Exception3ex3)就是3个参数表。
处理异常的操作由多条语句组成的,如,Processex1、Processex2、Processex3分别代表处理异常的语句。
(2)异常捕获和处理过程
如果执行语句Invokemethod3出现了一个异常,并且异常类型是Exception3,就会执行处理器三,并且系统将跳过Statement3;如果执行语句Invokemethod2出现了一个异常,并且异常类型是Exception2,就会执行处理器二,并且系统将跳过Statement2和Statement3;如果执行语句Invokemethod1出现了一个异常,并且异常类型是Exception1,就会执行处理器一,并且系统将会跳过Statement1、Statement2、Statement3三条语句。
如果异常不是这3种类型,程序就会立刻中止。
注意:
如果在图形程序中出现了Exception子类的一个实例,Java将在控制台上打印错误信息,但程序回到处理用户界面的循环继续运行,忽略该异常。
4.捕获和处理异常的方法
Java使用try-catch语句来捕获和处理异常,如图12-7所示。
图12-7在方法体中捕获和处理异常
下面的例子演示了方法calc()中声明和抛出异常。
在方法run()中捕获和处理异常。
【例12.2】不断给数组添加新成员,数组越界时抛出异常。
程序清单12-2ExceptionDemo.java
publicclassExceptionDemo
{privateinta[]=newint[10];
publicvoidaddNewMember()throwsException//在addNewMember()声明异常
{
for(inti=0,k=0;;i++,k++)
{
if(i>9)//数组越界则抛出异常
{thrownewException();}//抛出异常
else
{
System.out.println("k的值为:
"+k+"添加成功!
");
a[i]=k;//给数组添加新的成员
}
}
}
publicvoidcth()//在该方法中捕获并处理异常
{
try
{
addNewMember();
}
catch(Exceptione)//捕获到异常后在控制台上打印异常(即,处理异常)
{System.out.println("捕获到异常i>9,数组越界");}
}
publicstaticvoidmain(String[]args)
{ExceptionDemoE=newExceptionDemo();E.cth();}
}
程序运行结果如图12-8所示。
图12-8例子12.2运行结果
12.4重新抛出异常
方法执行时,如果没有语句捕获抛出的异常,程序将立刻退出该方法,控制权则返回到调用该方法的方法。
为了在退出方法之前让其执行某些任务,应该在方法中先捕获异常,待执行完任务后再重新抛出这个异常。
为了实现这一方案,常用的语句格式如图12-9所示。
图12-9在catch子句中重新抛出异常
【例12.3】在处理一些事情后重新抛出异常的示例。
程序清单12-3ReThrowException.java
publicclassReThrowException
{privateinta[]=newint[10];
publicvoidaddNewMember()throwsArrayIndexOutOfBoundsException//声明和抛出异常
{
for(inti=0,k=0;;i++,k++)
{
if(i>9)
{
thrownewArrayIndexOutOfBoundsException();//数组越界则抛出异常
}
else
{
System.out.println("k的值为:
"+k+"添加成功!
");
a[i]=k;//给数组添加新的成员
}
}
}
publicvoidcth()throwsArrayIndexOutOfBoundsException//捕获异常并,重新抛出异常
{
try
{
addNewMember();//被调用的方法可能抛出异常
}
catch(ArrayIndexOutOfBoundsExceptione)//捕获异常后做一些别的事,再重新抛出异常
{
System.out.println("捕获到异常i>9,数组越界");
System.out.println("可以在这里做一些别的事");
throwe;//重新抛出异常
}
}
publicstaticvoidmain(String[]args)
{try
{
ReThrowExceptionexception=newReThrowException();
exception.cth();
}
catch(Exceptione){System.out.println("重新捕获到异常i>9,数组越界");}
}
}
12.5finally子句
有时,无论是否出现异常,我们都希望在方法中执行某些代码,在这种情况下,就应该使用finally子句来达到这一目的。
1.try-catch-finally语句格式
try
{
//可能产生异常的语句写在这里
}
catch(ExceptionTypee)//捕获ExceptionType异常
{
//处理异常e的语句写在这里
}
finally
{
//无论是否出现异常,在本方法中必须执行的某些代码写在这里
}
执行try子句时,如果其中某个语句产生异常,根据异常的不同类型由匹配的catch子句对异常进行处理,处理完后控制权转到finally子句。
如果没有产生异常,则不执行任何catch子句,控制权直接转到finally子句。
可见,在try子句中无论是否捕获到异常,都必须执行finally子句中的代码。
注意:
可以有一个或多个catch子句,但至少要有一个catch子句。
2.try-catch-finally语句的应用
(1)try子句
通常将可能发生异常的语句放在try子句中。
例如,当某段代码需要访问某个文件,而程序运行时无法确定该文件是否存在,这时就要把这段代码放在try子句中。
这样当文件存在时程序可以正常运行;若文件不存在,则可以由catch子句捕获并处理异常。
(2)catch子句
catch子句的参数表由一个异常类型和一个异常对象构成。
异常类型必须为Throwable类的子类,它表明了catch子句所处理的异常类型;异常对象是指try子句中的语句产生的异常。
当try子句中的某个语句抛出异常后,系统从上向下分别对每个catch子句的异常类型进行检测,当找到某个catch子句的异常类型与抛出的异常对象类型匹配时,就执行匹配的catch子句,执行完后跳过其余catch子句,控制流转到finally子句。
类型匹配的含义是:
抛出的异常对象的类型与catch子句的异常类型或子类相同。
因此,为了捕捉到需要的异常,对多个catch子句的前后排列顺序是:
将参数类型是子类型的catch子句放在前面,将参数类型是超类型的catch子句放在后面。
也可以用一个catch子句处理多个异常类型,这时catch子句的参数类型应该是多个异常类型的超类。
提示:
类型是对各种java类的统称。
(3)finally子句
try子句中的某个语句抛出一个异常后,该语句后的其它语句不会被执行。
无论try子句中的代码是否抛出异常,也无论catch子句的异常类型是否与所抛出的异常的类型一致,finally子句中的代码都会执行。
该子句是可以省略的。
【例12.4】使用try-catch-finally语句对异常进行捕获和处理。
程序清单12-4FinallyDemo.java
publicclassFinallyDemo
{
publicstaticvoidmain(Stringargs[])
{
inti=0;
inta[]={5,6,7,8};
for(i=0;i<5;i++)
{try
{
System.out.print("a["+i+"]/"+i+"="+(a[i]/i));
}
catch(ArrayIndexOutOfBoundsExceptione)
{
System.out.print("捕获数组下标越界异常");
}
catch(ArithmeticExceptione)
{
System.out.print("捕获算术异常!
");
}
catch(Exceptione)
{
System.out.print("捕获"+e.getMessage()+"异常");
}
finally
{
System.out.println("finallyi_"+i);
}
}//for结束
System.out.println("继续");//因为前面对异常进行了捕获,所以
//本语句可以正常执行
}//方法结束
}//类结束
程序运行结果如图12-10所示。
图12-10FinallyDemo.java
当程序执行到i=4时,由于不存在数组元素a[4],系统抛出了数组越界异常对象;Java系统寻找到与数组越界异常匹配的catch(ArrayIndexOutOfBoundsExceptione)子句,对异常进行处理;然后跳过其余所有catch子句,执行finally子句;接着退出循环语句,继续执行其他语句。
12.6自定义异常类
虽然Java系统已经预定义了很多异常类,但是有时会遇到预定义的异常类不能描述出现的错误。
在这种情况下,程序员可以通过扩展Exception类及其子类来定义自己的异常类。
【例12.5】自定义异常类。
程序清单12-5CustomException.java
classFormatExceptionextendsException//自定义异常类FormatException
{privateStringErrorMessage;
publicFormatException(Stringmessage)
{super(message);
ErrorMessage=message;
}
publicStringgetMessage()
{return"该数组元素"+ErrorMessage+"不是偶数,不符合";}
}
publicclassCustomException//定义测试类
{intA[]={6,8