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;iSystem.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