Java学习总结之第八章 异常处理
一、Java异常处理机制概述
1、Java虚拟机用方法调用栈来跟踪每个线程中一系列的方法调用过程。
2、方法中的代码块可能抛出异常,有如下两种处理办法:
l 在当前方法中通过try…catch语句捕获并处理异常。例如:
public void methodA(int money){ try{ //以下代码可能会抛出SpecialException if(--money <= 0) throw new SpecialException("Out of money"); } catch(SpecialException e){ //异常处理 } } |
l 在方法的声明处通过throws语句声明抛出异常,返回给调用者来处理异常例如:
由于方法A不能捕获异常,所以,A就要通过throws语句声明抛出异常,由调用者methodB()来处理异常 public void methodA(int money)throws SpecialException{ //以下代码会抛出SpecialException if(--money <= 0) throw new SpecialException("Out of money"); } 此时methodB()的定义如下: public void methodB(int money){ try{ methodA(money); } catch(SpecialException e){ //处理异常 } } 如果methodB()也没有捕获SpecialException,而是声明抛出该异常,则Java虚拟机的处理流程将退回到methodB()方法的调用者,此时methonB()方法的定义如下: public void methodB(int money)throws SpecialException{ methodA(money); } |
3、当Java虚拟机追溯到调用栈的底部的方法时,如果仍然没有找到处理该异常的代码块,将按以下步骤处理:
l 调用异常对象的printStackTrace()方法,打印来自方法调用栈的异常信息。
l 如果该线程不是主线程,那么终止这个线程,其他线程继续正常运行。如果该线程是主线程(即方法调用栈的底部为main()方法),那么整个应用程序被终止。
二、运用Java异常处理机制
1、在Java语言中,用try…catch语句来捕获异常,格式如下:
try{ //可能会出现异常的代码 } catch(SQLException e){ //处理操纵数据库出现的异常 } catch(IOException e){ //处理操纵输入流和输出流出现的异常 } |
2、finally语句中的代码是在异常处理的任何情况下都必须执行的代码。finally代码块能保证特定的操作总是会被执行,它的形式如下:
try{ //可能会出现异常的代码 } catch(Exception e){ //处理异常 } finally{ //特定的操作 } |
3、不管try代码块中是否出现异常,都会执行finally代码块。
4、如果一个可能会出现异常,但没有能力处理这种异常,可以在方法声明处用throws子句来声明抛出异常。一个方法可能会出现多种异常,throws子句允许声明抛出多个异常,例如:
public void method()throws SQLException,IOException{…}
5、throw语句用于抛出异常,由throw语句抛出的对象必须是java.lang.Throwable类或者其子类的实例。
6、异常处理语句的语法规则:
l try代码块不能脱离catch代码块或finally代码块而单独存在。try代码块后面至少有一个catch代码块或finally代码块。
l try代码块后面可以有零个或多个catch代码块,还可以有零个或至多一个finally代码块。如果catch代码块和finally代码块并在,finally代码块必须在catch代码块后面。
l try代码块后面可以只跟finally代码块。
l 在try代码块中定义的变量的作用域为try代码块,在catch代码块和finally代码块中不能访问该变量。
l 当try代码块后面有多个catch代码块时,Java虚拟机会把实际抛出的异常对象依次和各个catch代码块声明的异常类型匹配,如果异常对象为某个异常类型或其子类的实例,就执行这个catch代码块,而不会再执行其他的catch代码块。
l 如果一个检查异常,要么用try…catch语句捕获,要么用throws语句声明将它抛出,否则会导致编译错误。
l throws语句后面不允许紧跟其他语句,因为这些语句永远不会被执行。
7、异常流程的运行过程:
l finally语句不会被执行的惟一情况是先执行了用于终止程序的System.exit()方法。exit()方法的定义如下:public static void exit(int status) ,exit()方法的参数status表示程序终止时的状态码,按照编程惯例,0表示正常终止,非零数字表示异常终止。
l return语句用于退出本方法。在执行try或catch代码块中的return语句时,假如有finally代码块,会先执行finally代码块。
l finally代码块虽然在return语句之前被执行,但finally代码块不能通过重新给变量赋值的方式来改变return语句的返回值。
l 建议不要在finally代码块中使用return语句,因为它会导致两种潜在的错误。第一种错误是覆盖try或catch代码块的return语句,第二种错误是丢失异常。
三、Java异常类
1、所有异常类的祖先类为java.lang.Throwable类,它的实例表示具体的异常对象,可以通过throw语句抛出,Throwable类提供了访问异常信息的一些方法,常用的方法包括:
l getMessage()——返回String类型的异常信息。
l printStackTrace()——打印跟踪方法调用栈而获得的详细异常信息。在程序调试阶段,此方法可用于跟踪错误 。
2、常见的运行时异常包括:NullPointerException、ClassCastException、ArithmeticException、IllegalArgumentException和IndexOutOfBoundsException,这种异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常时,即使没有用try…catch语句捕获它,也没有用throws子句声明它,还是会通过编译。
四、用户定义异常
1、异常转译
当位于最上层的子系统不需要关心来自底层的异常的细节时,常见的做法是捕获原始的异常,把它转换为一个新的不同类型的异常,再抛出新的异常,这种处理异常的办法称为异常转译。示例代码如下:
public void uploadImageFile(String imagePath)throws UpLoadException{ try{ //上传图像 }catch(IOException e){ //把原始异常信息记录到日志中,便于排错 throw new UpLoadException(); }catch(SQLException e){ //把原始异常信息记录到日志中,便于排错 throw new UpLoadException(); } } |
2、异常链
JDK1.4以上版本中的Throwable类支持异常链机制。所谓异常链就是把原始异常包装为新的异常类,也就是说在新的异常类中封装了原始异常类,这有助于查找产生异常的根本原因。如果使用JDK1.4以下的版本,可以由开发者自行设计支持异常链的异常类,以下示例代码提供了一种实现方案,JDK1.4以上版本中的Throwable类的实现机制与它很相似。
import java.io.*; public class BaseException extends Exception{ protected Throwable cause = null; public BaseException(){} public BaseException(String msg){super(msg);} public BaseException(Throwable cause){ this.cause = cause; } public BaseException(String msg,Throwable cause){ super(msg); this.cause = cause; } public Throwable initCause(Throwable cause){ this.cause = cause; return this; } public Throwable getCause(){ return cause; } public void printStackTrace(){ printStackTrace(System.err); } public void printStackTrace(PrintStream outStream){ printStackTrace(new PrintWriter(outStream)); } public void printStackTrace(PrintWriter writer){ super.printStackTrace(writer); if(getCause() != null){ getCause().printStackTrace(writer); } writer.flush(); } } 在BaseException中定义了Throwable类型的cause变量,它用于保存原始的Java异常。假定UploadException类扩展了BaseException类: public class UploadException extends BaseException{ public UploadException(Throwable cause){super(cause);} public UploadException(String msg){super(msg);} } 以下是把IOException包装为UploadException的代码。 try{ //上传图像文件 }catch(IOException e){ //把原始异常信息记录到日志中,便于排错 //把异常类包装为UploadException UploadException ue = new UploadException(e); throw ue; } |
3、处理多样化异常
Java方法中一次只能抛出一个异常对象,如果需要抛出多个异常,开发者必须自行设计支持多样化异常的异常类,以下为一种实现方案的示例代码:
import java.util.List; import java.util.ArrayList; import java.io.PrintStream; import java.io.PrintWriter; public class BaseException extends Exception{ protected Throwable cause = null; private List<Throwable> exceptions = new ArrayList<Throwable>(); public BaseException(){} public BaseException(String msg){super(msg);} public BaseException( Throwable cause ) { this.cause = cause; } public BaseException(String msg,Throwable cause){ super(msg); this.cause = cause; } public List getExceptions() { return exceptions; } public void addException( BaseException ex ){ exceptions.add( ex ); } public Throwable initCause(Throwable cause) { this.cause = cause; return this; } public Throwable getCause() { return cause; } public void printStackTrace() { printStackTrace(System.err); } public void printStackTrace(PrintStream outStream) { printStackTrace(new PrintWriter(outStream)); } public void printStackTrace(PrintWriter writer) { super.printStackTrace(writer); if ( getCause() != null ) { getCause().printStackTrace(writer); } writer.flush(); } } BaseException类包含了一个List类型的exceptions变量,用来存放其他的Exception。以下代码显示了BaseException的用法。 public void check()throws BaseException{ BaseException be = new BaseException(); try{ checkField1(); }catch(Field1Exception e){ be.addException(e); } try{ checkField2(); }catch(Field2Exception e){ be.addException(e) } if(be.getExceptions().size() > 0) throw be; } |
五、异常处理原则
1、异常处理只能用于非正常情况。
2、为异常提供说明文档。通过JavaDoc的@throws标签来描述产生异常的条件。
3、尽可能地避免异常。
4、保持异常的原子性。异常的原子性是指当异常发生时后,各个对象的状态能够恢复到异常发生前的初始状态,而不至于停留在某个不合理的中间状态。保持原子性有以下办法:
l 最常见的办法是先检查方法的参数是否有效,确保当异常发生时还没有改变对象的初始状态。
l 编写一段恢复代码,由它来解释操作过程中发生的失败,并且使对象状态回滚到初始状态。这种办法不是很常用,主要用于永久性的数据结构,比如数据库系统的回滚机制就采用了这种办法。
l 在对象的临时拷贝上进行操作,当操作成功后,把临时拷贝中的内容复制到原来的对象中。
5、避免过于庞大的try代码块。
6、在catch子句中指定具体的异常类型
7、不要在catch代码块中忽略被捕获的异常。