第5章 异常处理.docx
- 文档编号:10565093
- 上传时间:2023-02-21
- 格式:DOCX
- 页数:24
- 大小:37.71KB
第5章 异常处理.docx
《第5章 异常处理.docx》由会员分享,可在线阅读,更多相关《第5章 异常处理.docx(24页珍藏版)》请在冰豆网上搜索。
第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
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 第5章 异常处理 异常 处理