Java异常处理.docx
《Java异常处理.docx》由会员分享,可在线阅读,更多相关《Java异常处理.docx(18页珍藏版)》请在冰豆网上搜索。
Java异常处理
第10章Java异常处理
在前面的程序中,我们都假设程序处于最完美的情况,用户永远都不会以错误的形式输入数据、他们选择打开的文件必然存在、代码永远都不会有错……但事实中并非如此。
在现实世界中,肯定会存在错误的数据和错误的代码,所以必须掌握Java提供的异常处理(ExceptionHandle)机制,应付可能发生的问题。
10.1Java编程中的错误
错误是编程中不可避免和必须要处理的问题,编程人员和编程工具处理错误能力在很大的程度上影响着编程工作的效率和质量。
一般来说错误分为编译错误和运行错误两种。
10.1.1编译错误
Forpersonaluseonlyinstudyandresearch;notforcommercialuse
编译错误是由于编写的程序存在语法问题,未能通过源代码到目标码(在Java语言中是由源代码到字节码)的编译过程产生的,它由语言的编译系统负责检测和报告。
每种计算机高级语言都有自己的语言规范,编译系统就根据这个规范来检查编程人员所书写的源代码是否符合规定。
有的高级语言的语法规定得比较严格,如FORTRAN语言,对程序的格式有严格的要求;有的语言则给编程人员很大的自由度,如C语言,程序可以写得很灵活,使编程者可充分发挥他们的技巧和能力。
Java语言,由于是定位于网络计算的安全性要求较高的语言,它的语法规范设计得比较全面,相对于C语言增加了不少规定。
例如,数组元素下标越界检查,检查对未开辟空间对象的使用等。
由于更多的检查工作由系统自动完成,可以减少编程者的设计负担和程序中隐含的错误,提高初学者编程的成功率。
大部分编译错误是由于对语法不熟悉或拼写失误引起的,例如在Java语言中规定需在每个句子的末尾使用分号、标识符区分大小写,如果不注意这些细节,就会引发编译错误。
由于编译系统会给出每个编译错误的位置和相关的错误信息,所以修改编译错误相对较简单;但同时由于编译系统判定错误比较机械,在参考它所指出的错误地点和信息时应灵活地同时参照上下文其它语句,将程序作为一个整体来检查。
没有编译错误是一个程序能正常运行的基本条件,只有所有的编译错误都改正了,源代码才可以被成功地编译成目标码或字节码。
Forpersonaluseonlyinstudyandresearch;notforcommercialuse
10.1.2运行错误
一个没有编译错误的可执行的程序,距离完全正确还有一段距离,这是因为排除了编译错误,程序中可能还存在着运行错误。
运行错误是在程序的运行过程中产生的错误。
根据性质不同,运行错误还可以分为系统运行错误和逻辑运行错误。
Forpersonaluseonlyinstudyandresearch;notforcommercialuse
系统运行错误是指程序在执行过程中引发了操作系统的问题。
应用程序是工作在计算机的操作系统平台上的,如果应用程序运行时所发生的运行错误危及操作系统,对操作系统产生损害,就有可能造成整个计算机的瘫痪,例如死机、死循环等。
所以不排除系统错误,程序就不能正常地工作。
系统运行错误通常比较隐秘,排除时应根据错误的现象,结合源程序仔细判断。
例如,出现了死循环,就应该检测源程序中的循环语句和中止条件;出现死机,就应该检测程序中的内存分配处理语句等。
排除了系统运行错误,程序就可以顺利执行了,却仍然不代表它已经毫无问题了,因为程序中还有可能存在着逻辑运行错误。
逻辑运行错误是指程序不能实现编程人员的设计意图和设计功能而产生的错误,例如排序时不能正确处理数组头尾的元素等。
有些逻辑运行错误是由于算法考虑不周引起的,也有些则来自编码过程中的疏忽。
排序运行错误,包括系统运行错误和逻辑运行错误时,一个非常有效和常用的手段是使用开发环境所提供的单步运行机制和设置断点功能来分析程序运行过程,使之在人为的控制下边调试边运行。
在设计过程中,调试者可以随时检查变量中保存的中间量,设置临时运行环境,一步步地检查程序的执行过程,从而挖出隐藏的错误。
Forpersonaluseonlyinstudyandresearch;notforcommercialuse
10.2异常与异常类
10.2.1异常类结构与组成
异常(Exception),又称为例外,是特殊的运行错误对象,对应着Java语言特定的运行错误处理机制。
为了能够及时有效地处理程序中的运行错误,Java中引入了异常和异常类。
作为面向对象的语言,异常与其它语言要素一样,是面向对象规范的一部分,是异常类的对象。
Java中定义了很多异常类,每个异常类都代表了一种运行错误,类中包含了该运行错误的信息和处理错误的方法等内容。
每当Java程序运行过程中发生一个可识别的运行错误时,即该错误有一个异常类与之相对应时,系统都会产生一个相应的该异常类的对象,即产生一个异常。
一旦一个异常对象产生了,系统中就一定要有相应的机制来处理它,确保不会产生死机、死循环或它对操作系统的损害,从而保证了整个程序运行的安全性。
这就是Java的异常处理机制。
Java的异常类是处理运行时错误的特殊类,每一种异常类对应一种特定的运行错误。
所有的Java异常类都是系统类库中的Exception类的子类。
其继承的结构图如图10-1所示。
图10-1Exception类部分结构
在异常类层次的最上层有一个单独的类叫做Throwable,Throwable类是类库java.lang包中的一个类,这个类用来表示所有的异常情况。
每个异常类都是Throwable的子类或者子孙类。
Throwable有两个直接的子类。
一个是Exception,是用户程序能够捕捉到的异常情况;另一类是Error,它定义了通常无法捕捉到的异常。
在Java编程中,要谨慎使用Error类,因为它们通常会导致灾难性的失败。
其中Error类由系统保留,而Exception类则供应用程序使用。
同其他的类一样,Exception类有自己的方法和属性。
它的构造函数有两个:
publicException();
publicException(Strings);
其中第二个构造函数可以接受字符串参数传入的信息,该信息通常是对异常类所对应的错误的描述。
Exception类从父亲Throwable那里还继承了若干方法,其中常用的有:
1)publicStringtoString();
toString()方法返回描述当前Exception类信息的字符串。
2)publicvoidprintStackTrace();
printStackTrace()方法没有返回值,它的功能是完成一个打印操作,在当前的标准输出(一般就是屏幕)上打印输出当前异常对象的堆栈使用轨迹,也即程序先后调用执行了哪些对象或类的哪些方法,使得运行过程中产生了这个异常对象。
10.2.2系统定义的运行异常
Exception类有若干子类,如表10-1,每一个子类代表了一种特定的运行时错误。
这些子类有些是系统事先定义好并包含在Java类库中的,称为系统定义异常。
系统定义异常通常对应着系统运行错误。
由于这种错误可能导致操作系统错误甚至是整个系统的瘫痪,所以需要系统定义了异常类来表示这类错误。
表10-1中列出了常用的系统定义的异常。
表10-1部分常用系统定义的异常
系统定义的运行异常
异常对应的系统运行错误
ClassNotFoundException
未找到相应的类
ArrayIndexOutOfBoundsException
数组越界
FileNotFoundException
未找到制定的文件或目录
IOException
输入、输出错误
NullPointException
引用空的尚无内存空间的对象
ArithmeticException
算术错误
InterruptedException
一线程被其他线程打断
UnknownHostException
无法确定主机的IP地址
SecurityException
安全性的错误
MalformedURLException
URL格式错误
…
……
由于定义了相应的异常,Java程序即使产生一些致命的错误,如引用空对象等,系统也会自动产生一个对应的异常对象来处理和控制这个错误,避免其蔓延或产生更大的问题。
10.2.3用户自定义的异常
系统定义的异常主要用来处理系统可以预见的较常见的运行错误,对于某个应用程序所特有的运行错误,则需要编程人员根据程序的特殊逻辑在用户程序里自己创建用户自定义的异常类和异常对象。
这种用户自定义异常主要用来处理用户程序中特定的逻辑运行错误。
用户定义的异常通常采用Exception作为异常类的父类,一般用如下结构:
classMyExceptionextendsException{//自定义的异常类子类MyException
publicMyException(){//用户异常的构造函数
}
publicMyException(Strings){
super(s);//调用父类的Exception的构造函数
}
publicStringtoString(){//重载父类的方法,给出详细的错误信息
…
}
…
}
用户自定义异常用来处理程序中可能产生的逻辑错误,使得这种错误能够被系统及时识别并处理,而不致扩散产生更大的影响,从而使用户程序更为强健,有更好的容错性能,并使整个系统更加安全稳定。
创建用户自定义异常时,一般需要完成如下的工作:
1)声明一个新的异常类,使之以Exception类或其它某个已经存在的系统异常类或用户异常为父类。
2)为新的异常类定义属性和方法,或重载父类的属性和方法,使这些属性和方法能够体现该类所对应的错误的信息。
只有定义了异常类,系统才能够识别特定的运行错误,才能够及时的控制和处理运行错误,所以定义正确的异常类是创建一个稳定的应用程序的重要基础之一。
10.3异常的抛出
Java程序在运行时如果引发了一个可以识别的错误,就会产生一个与该错误相对应的异常类的对象,这个过程叫做异常的抛出,实际是相应异常类对象的实例的抛出。
根据异常类的不同,抛出异常的方式也有所不同。
10.3.1系统自动抛出异常
由系统定义的异常都是由系统自动的抛出,即一旦出现这些运行错误,系统将会为这些错误产生对应异常类的实例。
下面通过例10.1的程序来进行解释。
例10.1测试系统定义的运行异常示例程序SystemExceptionTest.java
publicclassSystemExceptionTest{
staticvoidProc(intb){
inta=10;
;
}
publicstaticvoidmain(Stringargs[]){
inti;
"thefristb=10:
");
i=10;
Proc(i);
"thesecondb=0:
");
i=0;
Proc(i);
}
}
上面是一个简单的JavaApplication程序,第一次除数是10,程序可以正常的运行,并得到结果1,第二次错误的以0为除数,运行过程中将引发ArithmeticException,这个异常是系统预先定义好的类,对应系统可以自动识别的错误,所以Java虚拟机遇到了这样的错误就会自动中止程序的运行,并新建一个ArithmeticException类的对象,即抛出了一个算术运行异常。
如图10-2所示。
图10-2系统定义的异常在运行中被抛出
10.3.2语句抛出的异常
一般用户自定义的异常不可能依靠系统自动抛出,而必须用Java语句抛出,throw语句用来明确的抛出一个“异常”。
首先,你必须知道什么样的情况算是产生了某种异常对应的错误,并应该为这个异常类创建一个实例,用throw语句抛出。
下面是throw语句的通常格式:
返回类型方法名(参数列表)throws要抛出的异常类名列表{
…
throw异常类实例;
…
}
其中throw用来抛出某个异常类对象,throws用来标明一个成员函数可能抛出的各种异常。
使用throw抛出异常时应注意以下的问题:
1)一般这种抛出语句应定义为满足某种条件时执行,例如往往把throw语句放在if语句中,当某个if条件满足时,才用throw语句抛出相应的异常,如:
if(I>100)
throw(newMyException());
2)含有throw语句的方法,应当在方法头定义中增加如下的部分:
throws要抛出的异常类名列表
这样做主要是为了通知所有欲调用这个方法的上层方法,准备接受和处理它在运行中可能会抛出的异常。
如果方法中的抛出的异常种类不止一个,则应该在方法头throws中列出所有可能的异常。
如上面的例子应该包含在这样的方法Mymethod中:
voidMyMethod()throwsMyException{//可能在程序中抛出MyException异常
…
if(I>100)
throw(newMyException());
…
}
若某个方法MyMethod可能产生Exception1、Exception2和Exception3三种异常,而它们又都是Super_Exception类的子类,如图10-3所示,则应在相应的方法中声明可能抛出的异常类:
图10-3异常类继承关系
voidMyMethod()throwsException1,Exception2,Exception3{
…//可能在程序中抛出这三个异常
}
除了以上这种声明抛出Exception1、Exception2和Exception3三种异常之外,还可以只简单地声明抛出Super_Exception。
下面这种方式和上面的是等价的。
voidMyMethod()throwsSuper_Exception{
…//可能在程序中抛出这三个异常的父类
}
在Java语言中如果调用了一个可能产生异常的方法,则当前的方法也可能会抛出这个异常,所以在当前的方法中也要对这个异常类进行声明,如下面程序,方法YourMethod()要调用上面定义的MyMethod():
voidYourMethod()throwsSuper_Exception{
…
MyMethod();//在程序中调用了可能会抛出异常的方法
…
}
3)Java语言要求所有用throws关键字声明的类和用throw抛出的对象必须是Throwable类或其子类。
Java开发环境提供了大量的错误和异常类,它们都是Throwable类或其子类,可以用来表示Java程序执行过程中出现的各类错误和异常。
如果你试图抛出一个不是可抛出(Throwable)对象,Java编译器将会报错。
下面一个程序中定义了用户异常并在程序中抛出异常,程序中要用到一个Employee雇员类,这个Employee类中有两个属性,分别是雇员的姓名m_EmpName和当前工资m_EmpSalary,同时在这个雇员类上加了一些限制,固定雇员的工资不得低于工资的最低标准800元,雇员每次工资的变化幅度不得高于原工资的20%。
一旦用户在程序中超出了这些规定,程序应该给出足够的错误信息,并保证程序的正确运行。
为了方便调试和运行,定义了两个用户异常IllegalSalaryException和IllegalSalryChangeException类分别处理上述的两种情况。
classIllegalSalaryExceptionextendsException{//用户定义的工资不合法异常
privateEmployeem_ConcernedEmp;//产生异常时的Employee类的引用
privatedoublem_IllegalSalary;
IllegalSalaryException(Employeeemp,doubleisal){//构造函数
super("工资低于最低工资");
m_ConcernedEmp=emp;
m_IllegalSalary=isal;
}
publicStringtoString(){//给出具体的错误信息
Strings;
s="为雇员提供的工资不合法:
雇员:
"+m_ConcernedEmp.getEmpName()
+"非法工资:
"+m_IllegalSalary
+"低于最低工资数额800元";
returns;
}
}
classIllegalSalaryChangeExceptionextendsException{//用户定义的工资变动不合法异常
privateEmployeem_ConcernedEmp;//产生异常时的Employee类的引用
privatedoublem_IllegalSalaryChange;
IllegalSalaryChangeException(Employeeemp,doublecsal){//构造函数
super("工资变动太大");
m_ConcernedEmp=emp;
m_IllegalSalaryChange=csal;
}
publicStringtoString(){//给出具体的错误信息
Strings;
s="为雇员提供的工资变动不合法:
雇员:
"+m_ConcernedEmp.getEmpName()
+"非法变动工资变化:
"+m_IllegalSalaryChange
+"高于原工资的20%";
returns;
}
}
此时,在雇员Employee类中,雇员的构造函数Employee()和修改工资的方法setEmpSalary()有可能抛出这两种异常,在方法头中就要用throws语句一一列举出来:
classEmployee{//Employee类
Stringm_EmpName;
doublem_EmpSalary;//雇员的姓名和工资
Employee(Stringname,doubleinitsal)throwsIllegalSalaryException{
//雇员类的构造函数,抛出异常
m_EmpName=newString(name);
if(initsal<800)
throw(newIllegalSalaryException(this,initsal));
m_EmpSalary=initsal;
}
publicStringgetEmpName(){
returnm_EmpName;
}
publicdoublegetEmpSalary(){
returnm_EmpSalary;
}
publicbooleansetEmpSalary(doublenewsal)//雇员工资修改函数
throwsIllegalSalaryException,IllegalSalaryChangeException
{//抛出两个异常
if(newsal<800)
throw(newIllegalSalaryException(this,newsal));
elseif(Math.abs(newsal-getEmpSalary())/getEmpSalary()>=0.2)
throw(newIllegalSalaryChangeException(this,newsal-getEmpSalary()));
else{
m_EmpSalary=newsal;
returntrue;
}
}
publicStringtoString(){//给出类实例的信息
Strings;
s="姓名:
"+m_EmpName+"工资:
"+m_EmpSalary;
returns;
}
}
对于上面的程序代码,在构造函数中,如果初始化雇员的工资小于定义的最低工资800元,则构造函数Employee()就会抛出IllegalSalaryException异常;在修改工资的函数中,有可能会抛出两种异常:
如果新工资newsal的值小于最低工资800元,则setEmpSalary()会抛出IllegalSalaryException异常;如果新工资的变动太大,超过原工资的20%,则setEmpSalary()会抛出IllegalSalaryChangeException异常。
10.4异常的处理
异常的处理主要包括捕捉异常,程序流程的跳转和异常处理语句块的定义。
10.4.1try…catch…finally块
Java语言提供了try…catch…finally语句来捕捉一个或多个异常,并进行处理。
try…catch…finally块语句的具体格式如下:
try{//可能出现异常的程序代码
语句1
…
语句n
}
catch(异常类型1,异常对象e1){
…//进行异常类型1的处理
}
catch(异常类型2,异常对象e2){
…//进行异常类型2的处理
}
catch(异常类型3,异常对象e3){
…//进行异常类型3的处理
}
…
finally{//其他处理程序代码
语句1
…
语句n
}
Java异常的捕捉和处理过程如下:
把程序中可能出现异常的语言包含在try引导的程序块中;在try{}之后紧跟一个或多个catch块,用于处理各种指定类型的异常;catch块后,可以跟一个finally程序块,该程序块中一般包含了用于清除程序现场的语句。
不论try块中是否出现异常,catch块是否被执行,最后都要执行finally块。
下面详细地介绍Java捕捉和处理异常的语句。
1)try语句块在try语句的{}中包含了可能会抛出一个或多个异常的一段程序代码,这些代码实际上指定了它后面的catch块所能捕捉的异常的范围。
Java程序运行到try块中的语句时如果产生了异常,就不再继续执行该try块中剩下的语句,而是直接进入第一个catch块中寻找与之匹配的异常类型并进行处理。
2)catch语句catch语句的参数类似于方法中的参数,包括一个异常类型和一个异常对象。
异常类型必须为Throwable类的子类,它指明了catch语句所处理的异常类型;异常对象则由Java运行时系统在try所指定的程序代码块中生成并捕获,大括号中包含异常对象的处理,其中可以调用对象的方法。
catch语句可以有多个,分别处理不同类型的异常。
Java运行时系统从上到下分别对每个catch语句处理的异常类型进行检测,直到找