假设一个Java程序运行期间出现了一个错误。为什么会报错呢?
-----文件包含了错误信息?
-----网络连接出现了问题?
-----使用了无效的数组下标?
-----试图使用一个没有被赋值的对象引用?
用户期望在出现错误而使得某些操作没有完成时,程序应该:
返回到一种安全状态,并能够让用户执行一些其他的命令
;或者允许用户保存所有操作的结果,并以妥善的方式终止程序
。
为了能够在程序中处理异常的情况,必须研究程序中可能会出现的错误和问题:
错误 | 分析 |
---|---|
用户输入错误 | 除了那些不可避免的键盘输入错误外,有些用户不遵守程序的要球。例如:假设有一个用户请求连接一个URL,而语法却不正确。在程序代码中应该对此进行检查,如果没有检查,网络层就会给出警告。 |
设备错误 | 在一个任务的处理过程中,硬件经常出现错误。 |
物理限制 | 磁盘满了,可用存储空间已被用完 |
代码错误 | 程序方法有可能无法正确执行。例如:方法可能返回了一个错误的答案;错误的调用了其他的方法;计算的数组索引不合法;试图在散列表中查找一个不存在的记录;试图 让一个空栈执行弹出操作…这些都属于代码错误。 |
一、异常概述
(一)异常分类
在Java程序设计语言中,异常对象都是派生于Throwable类的一个实例。如果Java中内置的异常类不够满足需求,用户可以创建自己的异常类。
- Error:描述了Java运行时系统的内部错误和资源耗尽错误。程序不应该抛出这种类型的错误。
- Exception:其分解为两个分支:一个分支派生于RuntimeException;另一个分支包含其他异常。
划分这两个分支的规则
是:由程序错误导致的异常属于RuntimeException;而程序本身没有问题,但由于像I/o错误这类问题导致的异常属于其他异常。
- 派生于RuntimeException的异常包含下面几种情况:
- 错误的类型转换
- 数组访问越界
- 访问null指针
- 不是派生于RuntimeException的异常包括:
- 试图在文件尾部后面读取数据
- 试图打开一个不存在的文件
- 试图根据给定的字符串查找Class对象,而这个字符串表示的类并不存在
(二)声明受查异常
Java语言规范将派生于Error类或RuntimeException类的所有异常称为非受查(unchecked)异常,所有其他的异常称为受查(checked)异常。
方法应该在其首部声明所有可能抛出的异常。这样可以从首部反映出这个方法可能抛出哪类受查异常。
举例:下面是标准库中提供的FileInputStream类的一个构造器的声明:
public FileInputStream(String name) throws FileNotFoundException{}
这个声明表示这个构造器将根据给定的String参数产生一个FileInputStream对象,但也有可能抛出一个FileNotFoundException异常。如果出错了,构造器将不会初始化一个新的FileInputStream对象,而是抛出一个FileNotFoundException类对象。
在自己编写方法时,不必将所有可能抛出的异常都进行声明。至于什么时候需要在方法中用throws子句声明异常,什么异常必须使用throws子句声明,需要记住在遇到下面4种情况时应该抛出异常:
- 调用一个抛出受查异常的方法,例如:FileInputStream构造器
- 程序运行过程中发现错误,并且利用throw语句抛出一个受查异常
- 程序出现错误,例如:a[-1]=0会抛出一个ArrayIndexOutOfBoundsException这样的非受查异常
- Java虚拟机和运行时库出现的内部错误。
-
如果出现前两种情况之一,需告诉调用这个方法的程序员有可能抛出异常。【for:任何一个抛出异常的方法都有可能是一个死亡陷阱;如果没有处理器捕获这个异常,当前执行的线程就会结束。】
-
对于可能被其他人使用的方法,应该根据异常规范,在方法的首部声明这个方法可能抛出的异常。
class MyAnimation { public Image loadImage(String s) throws IOException {...} }
-
如果一个方法有可能抛出多个受查异常类型,则需在方法的首部列出所有的异常类。每个异常类之间用逗号隔开。
class MyAnimation { public Image loadImage(String s) throws FileNotFoundException, EOFException {...} }
-
不需要声明Java的内部错误,即从Error继承的错误【任何程序代码都具有抛出这些异常的潜能,而我们对其没有任何控制能力】。同样,我们也不应该声明从RuntimeException继承的那些非受查异常。
void MyMythod(int i) throws ArrayIndexOutBoundsException { ... }
【总结:】一个方法必须声明所有可能抛出的受查异常,而非受查异常要么不可控制(Error),要么就应该避免发生(RuntimeException)。如果方法没有声明所有可能发生的受查异常,编译器就会发出一个错误消息。
(三)如何抛出异常?
对于一个已经存在的异常类,三步走:
- 找到一个合适的异常类;
- 创建这个类的一个对象;
- 将对象抛出。
String MyMethod(String message) throws IOEXception
{
...
while(...)
{
if(.....){//当满足一个异常条件是,抛出异常
throw new IOEXception();
}
}
return s;
}
(四)自定义异常类
自定义一个异常类是很简单的,我们只需要定义一个派生于Exception的类,或者派生于Exception子类的类。
例如:定义一个派生于IOException的类。
class MyException extends IOEXception
{
public MyException() {}
public MyException(String s) {
super(s);
}
}
现在,就可以抛出自己定义的异常类型了。
String MyMethod(String message) throws MyException
{
...
while(...)
{
if(.....){//当满足一个异常条件是,抛出自定义的异常
throw new MyEXception();
}
}
return s;
}
习惯上,定义的类应该包含两个构造器,一个是默认的构造器;另一个是带有详细描述信息的构造器(超类Throwable的toString方法将会打印出这些详细信息,这在调试中非常有用)。
【补充:API:java.lang.Throwable 1.0】
- Throwable()
构造一个新的Throwable对象,这个对象没有详细的描述信息。- Throwable(String message)
构造一个新的Throwable对象,这个对象有详细的描述信息。习惯上,所有的派生的异常类都支持一个默认的构造器和一个带有详细描述信息的构造器。- String getMessage()
获得Throwable对象的详细描述信息。
二、捕获异常
(一)捕获异常
如果某个异常发生的时候没有在任何地方进行捕获,那么程序就会终止执行,并在控制台上打印出异常信息,其中包含异常的类型和堆栈内容。
要想捕获一个异常,必须设置try/catch语句块。最简单的try语句块如下所示:
try {
...
}catch(ExceptionType e){
...//handler for this type
}
如果在try语句块中的任何代码抛出了一个在catch字句中说明的异常类,那么:
1)程序将跳过try语句块的其余代码。
2)程序将执行catch子句中的处理器代码。
如果在try语句块中的代码没有抛出任何异常,那么程序将跳过catch子句。
(二)捕获多个异常
在一个try语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理。
-
为每个异常类型使用一个单独的catch子句。
try{ }catch(FileNotFoundException e){ .... }catch(UnKnownHostException e){ .... }catch(IOException e){ .... }
【补充:】
e.getMessage();------得到详细的错误信息(如果有的话)
e.getClass().getName();------得到异常对象的实际类型 -
在Java SE7中,同一个字句中可以捕获多个异常类型。
try{ }catch(FileNotFoundException | UnKnownHostException e){ .... }catch(IOException e){ .... }
只有当捕获的异常类型彼此之间不存在子类关系时才需要这个特性。
捕获多个异常不仅会让代码看起来更简单,还会更高效。生成的字节码只包含一个对应公共catch子句的代码块。
(三)finally子句
try{
}catch(FileNotFoundException e){
....
}catch(UnKnownHostException e){
....
}catch(IOException e){
....
}finally{
....
}
不管是否有异常被捕获,finally子句中的代码都被执行。