第5章 异常处理.docx

上传人:b****8 文档编号:10565093 上传时间:2023-02-21 格式:DOCX 页数:24 大小:37.71KB
下载 相关 举报
第5章 异常处理.docx_第1页
第1页 / 共24页
第5章 异常处理.docx_第2页
第2页 / 共24页
第5章 异常处理.docx_第3页
第3页 / 共24页
第5章 异常处理.docx_第4页
第4页 / 共24页
第5章 异常处理.docx_第5页
第5页 / 共24页
点击查看更多>>
下载资源
资源描述

第5章 异常处理.docx

《第5章 异常处理.docx》由会员分享,可在线阅读,更多相关《第5章 异常处理.docx(24页珍藏版)》请在冰豆网上搜索。

第5章 异常处理.docx

第5章异常处理

第5章异常处理

5.1概述

早期的编程语言(比如C语言)没有异常(Exception)处理机制,通常是遇到错误返回一个特殊的值或设定一个标志,并以此判断是不是有错误产生。

随着系统规模的不断扩大,这种错误处理已经成为创建大型可维护程序的障碍。

于是在一些语言中出现了异常处理机制,比如Basic中的异常处理语句“onerrorgoto”,而Java则在C++基础上建立了全新的异常处理机制。

Java运用面向对象的方法进行异常处理,把各种不同的异常进行分类,并提供了良好的接口。

这种机制为复杂程序提供了强有力的控制方式。

同时这些异常代码与“常规”代码的分离,增强了程序的可读性,编写程序时也显得更为灵活。

Java程序开发人员在开发Java程序的时候要面对很多的问题,从获得可移植的代码一直到处理异常。

除了最简单的程序,稍微复杂的程序常会崩溃。

原因多种多样,从编程错误,错误的用户输入一直到操作系统的缺陷。

无论程序崩溃的原因是什么,程序开发者都有责任使得所设计的程序在错误发生后,要么能够恢复(在错误修复后能够继续执行),要么能合适地关闭(要尽力在系统终止前能够保存用户的数据)。

简而言之,异常是用来应对程序中可能发生的各种错误的一种强大的处理机制。

正确地使用异常,可以使程序易于开发、维护、远离bug、可靠性增强、易于使用。

反之,若异常运用不当,则会产生许多令程序开发人员头疼的事情:

程序难以理解和开发、产生令人迷茫的结果、维护变得非常的困难。

要写出友好,健壮的程序,灵活地运用Java程序语言的异常处理机制,须从以下几个角度来认识异常:

抛出异常、捕获异常以及处理异常。

本章将从这几个不同的方面来讨论Java语言的异常处理策略。

5.2异常处理

5.2.1遭遇异常

先来看下面的一段程序,见例5.2.1:

例5.2.1ReadFile.java

importjava.io.*;

importjava.util.*;

publicclassReadFile{

publicstaticvoidprintFile(StringfileName){

//try

//{

Vectorv=newVector();

BufferedReaderin;

Stringline;

in=newBufferedReader(newFileReader(fileName));

line=in.readLine();

while(line!

=null){

v.addElement(line);

line=in.readLine();

}

in.close();

for(inti=0;i

System.out.println(v.elementAt(i));

//}catch(FileNotFoundExceptione){

//System.out.println("FileNotFound"+e.getMessage());

//}catch(IOExceptione){

//System.out.println("IOException"+e.getMessage());

//}

}

publicstaticvoidmain(String[]args){

ReadFile.printFile("ReadFile.java");

}

}

类ReadFile中定义了一个静态方法printFile,该方法接收一个文件名作为参数,并在屏幕上打印出文件内容。

编译该程序,可以发现出现如下错误:

ReadFile.java:

7:

unreportedexceptionjava.io.FileNotFoundException;mustbecaughtordeclaredtobethrown

in=newBufferedReader(newFileReader(fileName));

^

ReadFile.java:

8:

unreportedexceptionjava.io.IOException;mustbecaughtordeclaredtobethrown

Stringline=in.readLine();

^

ReadFile.java:

11:

unreportedexceptionjava.io.IOException;mustbecaughtordeclaredtobethrown

line=in.readLine();

^

ReadFile.java:

13:

unreportedexceptionjava.io.IOException;mustbecaughtordeclaredtobethrown

in.close();

^

4errors

例5.2.1中,粗体表示的5个语句均会出现异常:

首先,BufferedReader的构建器会出现一个FileNotFoundException异常,表示给定的文件不存在。

其次,两条in.readLine()语句和in.close()语句均会出现IOException异常。

最后,v.elementAt(i)也可能出现ArrayIndexOutOfBoundsException异常,表示数组越界。

例如当i为负值或是大于v.size()-1时。

观察编译出错信息,可以发现,编译器指出:

FileNotFoundException和IOException必须被捕获(catch)或声明(declare)。

但是并没有对v.elementAt(i)可能出现的ArrayIndexOutOfBoundsException报错。

这是因为,在Java中,异常分为检查的(Checked)和未检查的(Unchecked)两种类型。

对于Checked类型的异常,编译器要求在方法中必须捕获之或是声明之;而对于Unchecked类型的异常,编译器并不强制方法捕获或是声明。

更多内容,见5.3小节。

由于FileNotFoundException和IOException均属于Checked类型的异常,因而编译器会强制要求捕获之或是声明之;而ArrayIndexOutOfBoundsException属于Unchecked类型的异常,因而编译器并不会强制要求捕获该异常或是声明该异常。

5.2.2捕获异常

例5.2.1现在还不能编译通过,可以有两种方法来解决该问题:

一种方法是捕获printFile方法中含有的Checked类型的异常,然后对捕获的异常进行处理;另一种方法是声明printFile方法抛出其中所含有的Checked类型的异常。

本小节讲述如何捕获并处理异常。

通常使用下面的代码框架来进行异常的捕获与处理:

try{

...//可能出现异常的代码

}catch(...){//捕获异常

...//异常处理代码

}

对于可能出现异常的代码,使用一个try块将其包括起来。

try块中可以包含一条或是多条Java语句。

对于例5.2.1,共有四条语句可能出现Checked类型的异常。

当然,你可以将每条语句包含在一个自己的try块中;也可以使用一个try块包同时含这四条语句,例如:

try{

Vectorv=newVector();

BufferedReaderin;

Stringline;

in=newBufferedReader(newFileReader(fileName));

line=in.readLine();

while(line!

=null){

v.addElement(line);

line=in.readLine();

}

in.close();

for(inti=0;i

System.out.println(v.elementAt(i));

}catch(...){//捕获异常

...//异常处理代码

}

这样,try块中的代码在执行时一旦出现异常,try块中剩余的代码将被跳过,出现的异常立刻由相应的catch语句捕获,并由catch块中的异常处理代码进行处理。

如果try中没有出现异常,那么catch语句中的代码不会得到执行。

√try语句至少有一个对应的catch块或是一个finally块。

catch块和finally块的内容详见本小节剩余部分。

一个try块可以有多个对应的catch块,用以捕获不同类型的异常:

try{

...

}catch(ExceptionType1name1){

...

}catch(ExceptionType2name2){

...

}

catch子句的一般形式为:

catch(ExceptionTypeexceptionName){

...

}

catch子句中包含有唯一的参数:

ExceptionTypeexceptionName

ExceptionType指明了catch语句所能捕获的异常类型,ExceptionType必须是一个继承了java.lang.Throwable的类。

当catch语句捕获一个异常时,将传递一个ExceptionType类型的对象进入catch块,该对象中包含了异常的全部信息,可以使用该对象中相应的方法获取异常信息,例如:

exceptionName.getMessage();//取得异常信息

exceptionName.printStackTrace();//打印异常信息栈

对于例5.2.1中会出现的FileNotFoundException和IOException异常,可以使用两个对应的catch块分别加以捕获:

catch(FileNotFoundExceptione){

System.out.println("FileNotFound"+e.getMessage());

}catch(IOExceptione){

System.out.println("IOException"+e.getMessage());

}

FileNotFoundException和IOException均为Throwable的子类,并且FileNotFoundException也是IOException的子类,如图5.2.1所示:

 

图5.2.1Java中的部分异常

需要注意的是,如果将上述的catch写成如下形式:

catch(IOExceptione){

System.out.println("IOException"+e.getMessage());

}catch(FileNotFoundExceptione){

System.out.println("FileNotFound"+e.getMessage());

}

编译器会报如下错误:

ReadFile.java:

22:

exceptionjava.io.FileNotFoundExceptionhasalreadybeencaught

}catch(FileNotFoundExceptione){

^

1error

其原因是:

catch块不仅可以捕获指定类型的异常,而且可以捕获该类型的所有子类类型的异常。

由于FileNotFoundException是IOException的子类,当出现FileNotFoundException类型的异常时,必定也是一个IOException类型的异常。

第一个catch块捕获IOException类型的异常已经包含了捕获FileNotFoundException类型的异常。

也即总是第一个catch块起作用,第二个catch块是多余的。

基于上述原因,我们完全可以使用下面的catch块来捕获一个所谓的“超级异常”,而不去精细地区分异常类型:

catch(Exceptione){

System.out.println("Exception"+e.getMessage());

}

由于Exception是所有异常的父类,因此不管try块中可能出现何种类型的异常,catch块总是能够捕获。

但是并不建议在任何情况下都使用这种方式来捕获异常,参见5.4小节-异常的捕获策略。

从上面的图中可以看出Java的几个特别重要的异常类:

Throwable:

所有异常的基类。

Error:

Throwable的子类,代表一个严重的问题。

例如:

●OutOfMemoryError代表JVM的堆空间耗尽。

●NoClassDefFoundError代表一个类没找到,或装入类时失败。

Exception:

Throwable的另一个子类,代表一个普通的问题。

例如:

●FileNotFoundException代表文件未找到。

●SQLException代表有关JDBC的异常。

RuntimeException:

Exception类的一个特殊的子类,可能在任何正常的操作中被抛出。

例如:

●NullPointerException表示试图引用null对象的方法或属性。

●IndexOutOfBoundException表示数组越界的异常。

在c语言中没有这样的特性,往往会造成严重且难以发现的程序漏洞。

√Throwable有两个子类,Error和Exception。

Error及其子类是描述Java运行系统中的内部错误(如VirtualMachineError)以及是资源耗尽(如OutOfMemoryError)等情况的。

应用程序对于Error这种情况的出现是无能为力的,所以,我们在开发应用程序时只关注Exeception及其子类。

在catch块后,还可以跟随finally块:

...

try{

...

}catch(...){

...

}finally{

...

}

...

无论try块中是否出现异常,finally块中的语句总是得到执行。

分三种情形来看:

(1)try块中的代码不抛出异常。

try块中所有的代码将被执行,随后执行finally块中的代码,最后执行finally块之后的代码。

(2)try块中的代码抛出异常,但有相应的catch语句捕获。

此种情形下,try块中抛出异常前的代码均被执行,剩下的代码被跳过;然后进入相应的catch块,catch块中的代码执行完毕后,执行finally块中的代码。

需要注意的是,如果在执行catch块中的语句时没有出现异常,在执行完finally块中的代码后,将继续执行finally块之后的代码;如果在执行catch块中的语句时也出现异常,这个异常会直接返回到该方法的调用者(当然,是在finally块中的语句执行完毕之后),finally块之后的代码将被跳过。

(3)try块中的代码抛出异常,但没有相应的catch语句捕获。

此种情形下,try块中抛出异常前的代码均被执行,剩下的代码被跳过;然后执行finally块中的代码;最后,将异常返回给方法的调用者;finally块之后的代码将被跳过。

5.2.3声明方法抛出异常

对于程序中可能出现的异常,可以使用5.2.2小节所述的第一种方法来加以处理:

捕获之然后加以处理。

然而,在有些情况下,仅根据当前的条件还无法处理出现的异常,这时候,就应该使用第二种方法:

声明该方法会抛出异常;该方法的调用者来负责捕获异常或是继续抛出异常。

例如在例5.2.1中,如果在printFile方法中不捕获FileNotFoundException及IOException异常,那么必须声明printFile方法会抛出这两种异常:

publicstaticvoidprintFile(StringfileName)

throwsFileNotFoundException,IOException

声明一个方法抛出异常,使用关键字throws,throws紧跟在方法签名之后。

可以同时声明方法抛出多个异常,多个异常之间使用逗号隔开。

例5.2.2ReadFile2.java

importjava.io.*;

importjava.util.*;

publicclassReadFile2{

publicstaticvoidprintFile(StringfileName)

throwsFileNotFoundException,IOException

{

Vectorv=newVector();

BufferedReaderin;

Stringline;

in=newBufferedReader(newFileReader(fileName));

line=in.readLine();

while(line!

=null){

v.addElement(line);

line=in.readLine();

}

in.close();

for(inti=0;i

System.out.println(v.elementAt(i));

}

publicstaticvoidmain(String[]args){

try{

ReadFile2.printFile("ReadFile2.java");

}catch(FileNotFoundExceptione){

System.out.println("FileNotFound"+e.getMessage());

}catch(IOExceptione){

System.out.println("IOException"+e.getMessage());}

finally{

System.out.println("finally");

}

}

}

throws关键字用来声明方法抛出异常。

在方法体中,如果需要显式抛出一个异常,使用关键字throw:

throwaThrowableObject;

aThrowableObject必须是一个“可抛出”的对象,也就是必须是由Throwable或是其子类所生成的对象。

例如:

publicstaticintdivide(inta,intb)throwsArithmeticException{

intresult;

if(b==0)thrownewArithmeticException("dividebyzero");

elseresult=a/b;

returnresult;

}

方法divide是对两个整数进行除法操作,当除数为零时,认为出现异常。

为此,需要在代码中抛出合适的异常。

我们选择了Java提供的一个算术异常来描述除数为零的情形。

如果在你的开发过程中遇到任何Java提供的异常类都不能描述的异常情况,还可以创建自己的异常类:

继承Exception或是其子类,并添加需要的内容。

例如:

classMyArithmeticExceptionextendsArithmeticException{

publicMyArithmeticException(){}

publicMyArithmeticException(StringerrorDescription){

super(errorDescription);

}

}

这样,就可以使用自定义的异常类了:

publicstaticintdivide(inta,intb)throwsMyArithmeticException{

intresult;

if(b==0)thrownewMyArithmeticException("dividebyzero");

elseresult=a/b;

returnresult;

}

5.3异常的抛出策略

本小节讨论异常抛出的策略。

首先看下面的一段示例代码:

例5.3.1CustomMath.java

classMyArithmeticExceptionextendsArithmeticException{

publicMyArithmeticException(){}

publicMyArithmeticException(StringerrorDescription){

super(errorDescription);

}

}

publicclassCustomMath{

publicstaticintdivide(inta,intb)throwsMyArithmeticException{

intresult;

if(b==0)thrownewMyArithmeticException("dividebyzero");

elseresult=a/b;

returnresult;

}

publicstaticvoidmain(String[]args){

try{

intc=CustomMath.divide(10,0);

}catch(MyArithmeticExceptione){

e.printStackTrace();

}

}

}

当方法divide抛出MyArithmeticException异常时,它同时“抛出”了三方面的信息:

●异常的类型,这里是MyArithmeticException。

●发生异常的位置,可以通过异常的printStackTrace()方法得到。

●异常的信息,在这里是通过指定errorDescription字符串(“dividebyzero”)来表达的。

这三方面的信息分别对应着三种消息的“接收者”:

●异常的类型——对于divide方法的调用者有特别重要的意义。

调用divide方法的程序可以通过捕获特定类型的异常(如MyArithmeticException)而忽略其它类型异常。

●发生异常的位置——对于程序员或客户技术支持来说有着特别重要的意义。

他们需要通过stacktrace信息来分析错误或调试程序。

●异常的信息——对于那些解释错误信息的用户来讲有着特别重要的意义。

所以,当程序抛出一个异常的时候,必须确保所有的异常“接收者”都收到有意义的信息。

也就是说,必须选择合适的异常类型,以便方法的调用者程序可以根据异常的类型来作出正确的处理;必须设置有意义的异常信息,以便看到异常或日志记录的用户能明白发生了什么事;必须让stacktrace反映出异常发生的最原始的位置信息。

一个方法所声明抛出的异常,是设计该方法时必须考虑的重要因素。

程序员应该站在方法调用者的立场去考虑这个问题,而不是站在书写这个方法的开发者的立场:

●哪些异常是对调用者有意义的?

调用者可以方便地捕获并处理这些异常。

●哪些异常是调用者应当忽略的?

调用者可以把这些异常传递给它们的调用者或用户。

5.3.1不要声明抛出所有异常

声明所有可能产生的异常,是极不明智的做法。

以下面的getResour

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 经济学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1