第9章 异常处理
一、Java异常处理机制概述
1、Java异常处理机制的优点
(1)把各种不同类型的异常情况分类,用Java类来表示异常情况,称为异常类。
(2)异常流程的代码与正常流程代码分离,提高程序的可读性,结构清晰。
(3)可以更灵活地处理异常,如果当前方法有能力处理异常,就捕获并处理,否则只需要抛出异常,由方法调用者去处理。
示例:车子出故障的异常
/*设计一个异常类,表示车子故障的异常情况*/
public class CarWrongException extends Exception{
public CarWrongException(){};
public CarWrongException(String msg){
super(msg);
}
}
/*该异常类的应用*/
public class car{
public void run() throws CarWrongException{
if(汽车无法刹车)
throw new CarWrongException("汽车无法刹车");
if(发动机无法启动)
throws new CarWrongException("发动机无法启动");
}
}
示例:上班迟到的异常
/*设计一个表示上班迟到的异常类*/
import java.util.Date;
public class LateException extends Exception{
private Date arriveTime; //迟到时间
private String reason; //迟到原因
public LateException(Date arriveTime,String reason){
this.arriveTime=arriveTime;
this.reason=reason;
}
public Date getArriveTime(){
return this.arriveTime;
}
public String getReason(){
return this.reason;
}
}
/*上述两个异常类的应用*/
public class Worker{
private Car car;
public Worker (Car car){
this.car=car;
}
public void gotoWork()throws LateException{
try{
car.run();
}catch(CarWrongException e){//处理汽车故障的异常
walk();
Date date=new Date(System.currentTimeMillis());
String reason=e.getMessage();
throw new LateException(date,reason);
//创建一个迟到类异常对象,并抛出
}
}
public void walk(){}
}
2、Java虚拟机的方法调用栈
Java虚拟机用方法调用栈(method invocation stack)来跟踪每个线程中一系列的方法调用过程,该堆栈保存了每个调用方法的本地信息,每个线程都有一个独立的方法调用栈。对于Java程序的主线程,其堆栈底部就是其入口方法main()。当一个新方法被调用时,JVM把描述该方法的栈结构置入栈顶,位于栈顶的方法就是当前正在执行的方法。
方法中代码块抛出异常,有以下两种处理方法:
(1)在当前方法中使用try{..}catch(){}捕捉并处理。
(2)在方法声明处通过throws语句声明抛出异常。
当一个方法执行完,其方法栈结构会从方法调用栈弹出,然后处理前一个方法。如果执行方法时候出现异常,则JVM先看当前方法中是否有catch代码块能捕捉该异常,如果没有则在方法调用栈中弹出该方法栈结构,继续到前一方法中查找合适的catch代码块。当JVM追溯到调用栈底部方法仍没有找到处理该异常的代码块,则按以下步骤处理:
(1)调用异常对象的printStackTrace()方法,打印异常信息。
(2)如果该线程不是主线程,则终止该线程,其他线程继续正常运行。如果该线程是主线程,则整个应用程序被终止。
二、运用Java异常处理机制
1、try{..}catch(){..}
try{
可能会出现异常的代码块
}catch(异常类参数){
处理异常的代码块
}
2、finally语句:任何情况下都必须执行的代码
由于异常会强制中断正常流程,而有一些情况,不管出现什么异常都必须执行一些指令,比如:
public void work()throw LeaveEarlyException{
try{
工作8小时;(可能会抛出LeaveEarlyException异常)
关店门;
}catch(DiseaseException e){
throw new LeaveEarlyException();
}
对于上述代码,关店门这个操作是必须执行的,而当店员生病出现异常了这个操作会被系统自动忽略。所以这种情况下可以使用finally语句。
public void work()throws LeaveEarlyException{
try{
工作8小时;(可能会抛出LeaveEarlyException异常)
}catch(DiseaseException e){
throw new LeaveEarlyException();
}finally{//不管是否出现异常,该代码块都会执行
关店门;
}
3、throws子句,异常声明:声明可能会出现的异常
如果一个方法可能会出现异常,但确定没有能力处理这种异常,可以在方法声明处用throws语句声明该抛出异常。
示例,Car类本身无法处理该异常,定义run()方法时声明该异常:
public void run()throws CarWrongException{
if(车子无法刹车)throw CarWrongException("车子无法刹车");
if(发动不了)throw CarWrongException("发动不了");
}
Worker类的gotoWork()方法调用上述run(),并且gotoWork()捕获并处理上述CarWrongException异常,但处理过程中又产生了新的异常LateException(),而其无法处理该异常,所以在方法定义时声明该抛出异常。
public gotoWork()throws LateException{
try{
run();
}catch(CarWrongException e){//处理CarWrongException
walk();
Date date=new Date(System.currentTimeMillis());
String reason=e.getMessage;
throw new LateException(date,reason);
//创建LateException异常类的对象,并抛出
}
}
异常的声明可以使得包含该异常声明的方法的调用者了解调用可能引起的异常,从而采取相应措施(捕获并处理)或者声明继续抛出。
4、throw语句:抛出异常
由throw语句抛出的异常类的对象必须是java.lang.Throwable类或其子类的实例。
5、异常处理语句的语法规则
异常处理语句主要涉及到throws,throw,try,catch,finally关键字,须注意:
(1)try代码块不能单独存在,必须与catch或者finally代码块一起使用。如果和catch、finally代码块一起使用,finally必须放在最后。
(2)try/catch/finally三个代码块中的变量作用域各自独立。
(3)如果try语句后面有多个catch代码块,则JVM会把try代码块中产生的异常对象依次与catch中异常类型匹配,如果匹配则执行该catch代码块,而不再执行其他catch代码块。
(4)如果一个方法可能出现受检查异常,要么用try-catch语句捕获,要么用throws声明抛出,否则会导致编译错误。
6、异常流程的运行过程
(1)finally语句只有在用于终止程序的System.exit()方法先执行这一种情况下才不被执行。java.lang.System的exit()静态方法用于终止当前JVM进程,JVM所执行的Java应用也随之终止。
(2)return语句用于退出当前方法,在try或catch中执行return后,会先执行fianlly代码块后再退出。
三、异常类
程序运行中任何中断正常流程的因素都被认为是异常,按照面向对象的思想,Java语言用Java类描述异常,所有异常类的祖先类为java.lang.Throwable类,它的实例可以被throw语句抛出,同时Throwable类提供了访问异常信息的一些方法,包括:
*getMessage():返回String类型的异常信息。
*printStackTrace():打印跟踪方法调用栈而获得的详细异常信息。
示例:
public class ExTrace{
public void methodA(int money)throws SpecialException{
if(--money<=0)
throw new SpecialException("Out of money");
}
public void methodB(int money)throws Exception{
try{
methodA(1);
}catch(SpecialException e){
System.out.println("--Output of methodB()-");
System.out.println(e.getMessage());
throw new Exception("Wrong");
}
}
public static void main(String[] args){
try{
new ExTrace().methodB(1);
}catch(Exception e){
System.out.println("--Output of main()-");
e.printStackTrace();
}
}
}
Throwable类有两个直接子类:
(1)Error类:表示仅靠程序本身无法恢复的严重错误,比如内存空间不足或者JVM的方法调用栈溢出。在遇到这样的错误时通常建议让程序终止。
(2)Exception类:表示程序可以处理的异常。
1、运行时异常
RuntimeException类及其子类都为运行时异常,其特点是Java编译器不会去检查,即当可能出现这种异常时编译也是可以通过的。
2、受检查异常
其特点是编译器会检查,当可能出现这种异常时,要么用try..catch语句去捕获它,要么用throws声明该抛出异常,否则编译无法通过。
3、区分运行时异常和受检查异常
受检查异常表示程序可以处理的异常,如果抛出该异常的方法不能处理,那么方法的调用者应该去处理。
四、用户定义异常
示例:定义一个服务器超时异常
public class ServerTimeOutException
extends Exception{
private String reason;
private int port;
public ServerTimeOutException(String reason,int port){
this.reason=reason;
this.port=port;
}
public String getReason(){
return reason;
}
public int getPort(){
return port;
}
}
以下代码使用throw语句抛出上述异常。
//不能连接80端口
throw new ServerTimeOutException("could not connect",80);