异常处理
异常处理机制是Java
语言中一个独特之处,它为开发稳定的软件系统提供了有力的支持。它主要使用捕获异常和声明抛弃异常两种方法来处理程序中可能出现异常的语句块,其中捕获异常的方法是一种积极地处理异常的方法,而声明抛弃异常是一种消极地处理异常的方法。当Java
内置的异常都不能明确地说明异常情况的时候,需要创建自己的异常。
一. 异常处理基础
异常就是在程序的运行过程中所发生的异常事件,它中断指令的正常执行。Java
提供了一种独特地处理异常的机制,通过异常来处理程序设计中出现的错误。当程序运行出现异常时,Java
运行环境就用异常类Exception
的相应子类创建一个异常对象,并等待处理。异常对象可以调用方法得到或输出有关异常的信息。
1. 异常类的层次
在JDK中,每个包中都定义了异常类,而所有的异常类都直接或间接地继承于Throwable类。
异常类的继承关系
2. 异常类的分类
Java
中的异常类可分为两大类:Error
和Exception
。
Error
---动态链接失败,虚拟机错误等,通常Java
程序不应该捕获这种异常,也不会抛弃这种异常。
Exception
---包括运行时异常和非运行时异常。
-
(1) 运行时异常
继承于RuntimeException
的类都属于运行时异常,例如算术异常(除零错)、数组下标越界异常等。由于这些异常产生的位置是未知的,Java
编译器允许程序员在程序中不对它们做出处理。 -
(2) 非运行时异常
除了运行时异常之外,其他由Exception
继承来的异常类都是非运行时的异常,例如FileNotFoundException
(文件未找到异常)。Java编译器要求在程序中必须处理,捕获或者声明抛弃异常。
1.Error体系类型异常的特点
Error
类体系描述了Java
运行系统中的内部错误以及资源耗尽的情形。应用程序不应该抛出这种类型的对象(一般是由虚拟机抛出)。如果出现这种错误,除了尽力使程序安全退出外,在其他方面是无能为力的。所以在进行程序设计时,应该更关注Exception体系。
2.Exception体系类型异常的特点
Exception
体系包括RuntimeException
体系和其他非RuntimeException
的体系。
- (1)
RuntimeException
体系包括错误的类型转换、数组越界访问和试图访问空指针等。处理RuntimeException
的原则是:如果出现RuntimeException
,那么一定是程序员的错误。例如可以通过检查数组下标和数组边界来避免数组越界访问异常。
(2) 其他(IOException等)
类异常一般是外部错误,例如试图从文件尾后读取数据等,这并不是程序本身的错误,而是在应用环境中出现的外部错误。
异常的处理
java
语言中有两种异常处理机制:捕获异常
和声明抛弃异常
1. 捕获异常
当Java
运行环境得到一个异常对象时,它将会沿着方法的调用栈逐层回溯,寻找处理这一异常的代码,找到能够处理这种类型的异常的方法后,运行环境把当前异常对象交给这个方法进行处理,这一过程称为捕获(catch)异常
。这是积极的异常处理机制。如果Java
运行环境找不到可以捕获异常的方法,则运行环境将终止,相应的Java程序也将退出。
2. 声明抛弃异常
如果一个方法并不知道如何处理所出现的异常,则可在方法声明时,声明抛弃(throw)异常
。这是一种消极的异常处理机制。
一. 捕获异常
捕获异常是通过try-catch-finally
语句实现的。
1. try
捕获异常的第一步是用try{...}
选定捕获异常的范围,由try
所限定的代码块中的语句在执行过程中可能会生成异常对象并抛弃。
2. catch
每个try
代码块可以伴随一个或多个catch
语句,用于处理try
代码块中所生成的异常事件。catch
语句只需要一个形式参数指明它所能够捕获的异常类型,这个类必须是Throwable
的子类,运行时系统通过参数值把被抛弃的异常对象传递给catch
块。catch
块是对异常对象进行处理的代码,与访问其他对象一样,可以访问一个异常对象的变量或调用它的方法。getMessage()
是类Throwable
所提供的方法,用来得到有关异常事件的信息,类Throwable
还提供了方法printStackTrace()
用来跟踪异常事件发生时执行堆栈的内容。
try {
...
} catch (FileNotFoundException e) {
System.out.println(e);
System.out.println("message:" + e.getMessage());
e.printStackTrace(System.out);
} catch (IOException e) {
System.out.println(e);
}
catch
语句的顺序:
捕获异常的顺序和catch
语句的顺序有关,当捕获到一个异常时,剩下的catch
语句就不再进行匹配。因此在安排catch
语句的顺序时,首先应该捕获最特殊的异常,然后再逐渐一般化。也就是一般先安排子类,再安排父类。
3. finally
捕获异常的最后一步是通过finally
语句为异常处理提供一个统一的出口,使得在控制流转到程序的其他部分以前能够对程序的状态做统一的管理。不论在try
代码块中是否发生了异常事件,finally
块中的语句都会被执行。
二. 声明抛弃异常
1. 声明抛弃异常
如果在一个方法中生成了一个异常,但是这一方法并不确切地知道该如何对这一异常事件进行处理,这时一个方法就应该声明抛弃异常,使得异常对象可以从调用栈向后传播,直到有合适的方法捕获它为止。
声明抛弃异常是在一个方法声明中的throws字句中指明的。例如:
public int read() throws IOException {
}
throws子句中同时可以指明多个异常,之间由逗号隔开。例如:
public static void main(String[] args) throws IOException,IndexOutOfBoundsException {
}
2. 抛出异常
抛出异常就是产生异常对象的过程,首先要生成异常对象。异常或者由虚拟机生成,或者由某些类的实例生成,也可以在程序中生成。在方法中,抛出异常对象是通过throw
语句实现的。例如:
IOException e = new IOException();
throw e;
可以抛出的异常必须是Throwable或其子类的实例。
三. 自定义异常类
当Java
内置的异常都不能明确地说明异常情况的时候,开发人员往往需要定义一些异常类用于描述自身程序中的异常信息,以区分其他程序的异常信息。需要注意的是,唯一有用的就是类型名这个信息,所以不要在异常类的设计上花费精力。
实现自定义异常类的方法如下:
- (1) 类
java.lang.Throwable
是所有异常类的基类,它包括两个子类:Exception
和Error
,Exception
类用于描述程序能够捕获的异常,如ClassNotFoundException
。Error
类用于指示合理的应用程序不应该试图捕获的严重问题,如虚拟机错误。 - (2) 自定义异常类必须是
Throwable
的直接或间接子类。自定义异常类可以继承Throwable
类或者Exception
,而不要继承Error
类,自定义异常类之间也可以有继承关系。 - (3) 需要为自定义异常类设计构造方法,以方便构造自定义异常对象。
一个方法所声明抛弃的异常是作为这个方法与外界交互的一部分而存在的,所以方法的调用者必须了解这些异常,并确定如何正确地处理它们
1. 继承Exception
public class MyFirstException extends Exception {
public MyFirstException() {
super();
}
public MyFirstException(String msg) {
super(msg);
}
public MyFirstException(String msg,Throwable cause) {
super(msg,cause);
}
public MyFirstException(Throwable cause) {
super(cause);
}
}
自定义异常类的主要作用是区分异常发生的位置,当用户遇到异常时,根据异常名就可以知道哪里有异常,根据异常提示信息进行修改
2. 继承Throwable类
public class MySecondException extends Throwable {
public MySecondException() {
super();
}
public MySecondException(String msg) {
super(msg);
}
public MySecondException(String msg,Throwable cause) {
super(msg,cause);
}
public MySecondException(Throwable cause) {
super(cause);
}
}
当一个try
块后面跟着多个catch
块时,如果发生的异常匹配第一个catch
块的参数,便将异常处理权利交给第一个catch
块;如果发生的异常与第一个catch
块不匹配,便看是否与第二个catch
块匹配,依次下去,如果到最后依然无法匹配该异常,便需要在方法声明中添加一条throw
语句,将该异常抛出。
因此,在有多个catch
块,而且每次处理的异常类型具有继承关系时,应该首先catch
子类异常,再catch
父类异常。