什么是异常
# 什么是异常
程序运行的时候,经常会发生各种错误。例如,当你尝试把 0 作为除数,Java 程序是处理的不了结果的,因为违反了运算规则,出现异常。本章我们讨论如何在 Java 程序中处理各种异常情况。
# 异常概述
异常发生的原因很多,有些是用户造成的(例如输入除数的时候,输入了 0),或者读取某个文件的时候,文件已经被删除了,这样的情况下,Java 程序都不能正常得到结果,会发生错误。
还有一些错误是随机出现,并且永远不可能避免的。比如:
- 网络突然断了,连接不到远程服务器;
- 内存耗尽,程序崩溃了;
- 用户点“打印”,但根本没有打印机;
- ……
所以,一个健壮的程序必须处理各种各样的错误。
所谓错误,就是程序调用某个函数的时候,如果失败了,就表示出错。而如何知道调用失败的原因呢?我们可以约定错误码(每个错误码对应一个具体的错误信息)。
例如,处理一个文件,如果返回 0
,表示成功,返回其他整数,表示约定的错误码:
int code = processFile("C:\\test.txt");
if (code == 0) {
// ok:
} else {
// error:
switch (code) {
case 1:
// file not found:
case 2:
// no read permission:
default:
// unknown error:
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
因为使用 int
类型的错误码,想要处理就非常麻烦。这种方式常见于底层 C 函数。
# Java 中的异常
如果全靠自己手工处理异常,得增加很多工作量,并且各个项目之间的异常处理都不一样,没有一个标准。
为此,Java 内置了一套异常处理机制,总是使用异常来表示错误,这些错误会作为异常抛出。
异常是一种 class
,它本身带有类型信息,表明阻止正常进行程序的错误或情况,如果异常没有被处理,程序就会非正常终止。
异常的继承关系如下:
┌───────────┐
│ Object │
└───────────┘
▲
│
┌───────────┐
│ Throwable │
└───────────┘
▲
┌─────────┴─────────┐
│ │
┌───────────┐ ┌───────────┐
│ Error │ │ Exception │
└───────────┘ └───────────┘
▲ ▲
┌───────┘ ┌────┴──────────┐
│ │ │
┌─────────────────┐ ┌─────────────────┐┌───────────┐
│OutOfMemoryError │... │RuntimeException ││IOException│...
└─────────────────┘ └─────────────────┘└───────────┘
▲
┌───────────┴─────────────┐
│ │
┌─────────────────────┐ ┌─────────────────────────┐
│NullPointerException │ │IllegalArgumentException │...
└─────────────────────┘ └─────────────────────────┘
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 异常的种类
为了方便,我们可以人为地将异常分为三种类型:系统错误 Error
、运行时异常 RuntimeException
和 非运行时异常 Exception
。
# Error
系统错误( system error)是由 Java 虚拟机抛出的,用 Error 类表示。
Error 类描述的是内部系统错误,这样的错误很少发生。如果发生,除了通知用户以及尽量稳妥地终止程序外,几乎什么也不能做,例如:
OutOfMemoryError
:内存耗尽NoClassDefFoundError
:无法加载某个 ClassStackOverflowError
:栈溢出LinkageError
:一个类对另一个类有某种依赖性,但是在编译前者后,后者进行了修改,变得不兼容virtualMachineError
:Java 虚拟机崩溃,或者运行所必需的资源已经耗尽
# RuntimeException
运行时异常描述的是程序设计错误,通常是在程序运行时,由 Java 虚拟机抛出的,例如:
NullPointerException
:对某个null
的对象调用方法或字段。例如变量还未初始化就使用IndexOutOfBoundsException
:数组索引越界InputMismatchException
:比如你希望用户输入整数,但是用户输入了小数,在转换为数字的时候就会有异常。ArithmeticException
:一个整数除以 0。注意,浮点数的算术运算不抛出异常,因为浮点数不是很精确,难以真正的表示 0。IllegalArgumentException
:传递给方法的参数非法或不合适
# Exception
这里指的 Exception
不包含 RuntimeException
,它描述的是由程序和外部环境所引起的错误,例如:
ClassNotFoundException
:试图使用一个不存在的类。例如,如果试图使用命令 java 来运行一个不存在的类,或者程序要调用三个类文件而只能找到两个,都会发生这种异常IOException
:同输入/输出相关的操作,例如,无效的输入、读文件时超过文件尾、打开一个不存在的文件等。IOException
的子类的例子有InterruptedIOException
.EOFException
(EOF 是 End of File 的缩写)和FileNotFoundException
# 什么异常应该被处理
Error
、RuntimeException
,以及他们的子类都是免检异常(unchecked Exception),也就是可以不处理的- 除了免检异常,其他异常都是必检异常(checked Exception),必须使用 try-catch 块处理,或者在方法头进行声明(下一节会讲)。
大部分情况下,免检异常(RuntimeException
及其子类)都是由于逻辑错误造成的,例如:
NullPointerException
:对某个null
的对象调用方法或字段。例如变量还未初始化就使用,这是逻辑错误;IndexOutOfBoundsException
:数组索引越界,例如一个数组只有 10 个元素,但程序访问其第 11 个元素,这也是代码有问题;ArithmeticException
:一个整数除以 0,基本的四则运算规则都违背了,应该判断除数是否为 0;- …………
综上,免检异常可能在程序的任何一个地方出现。为避免过多地使用异常处理代码, Java 语言不强制要求编写代码捕获或声明免检异常。
但是必检异常,通常就不是程序问题了,而是外部的问题,例如:
IOException
:要操作的文件不见了,或者没权限等等SocketException
:读取网络失败,比如网络突然断了掉线了
必检异常,通常是我们可以处理的。例如网络异常,我们可以捕捉到异常,然后告知用户是网络异常;或者 IOException
,比如你在重命名一个正在打开的文件,也会有提示。
注意:编译器对 RuntimeException
及其子类不做强制捕获要求,但不是指应用程序本身不应该捕获并处理 RuntimeException。是否需要捕获,具体问题具体分析。
# 小结
本文我们介绍了如下内容:
- 什么是异常
- Java 中的异常体系
- 异常的分类
- 什么异常应该被处理
异常也是 Java 中很基本的概念,并且在工作中会经常需要和处理异常,请读者好好掌握。