6.2.异常引发
\(6.2\)异常引发
1.\(throw\)语句
考虑下面这个实现\(ArraySet\)的程序: 1
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
27
28
29
30
31
32
33
34
35
36import java.util.Iterator;
public class ArraySet<T> implements Iterable<T> {
private T[] items;
private int size; // the next item to be added will be at position size
public ArraySet() {
items = (T[]) new Object[100];
size = 0;
}
/* Returns true if this map contains a mapping for the specified key.
*/
public boolean contains(T x) {
for (int i = 0; i < size; i += 1) {
if (items[i].equals(x)) {
return true;
}
}
return false;
}
/* Associates the specified value with the specified key in this map. */
public void add(T x) {
if (contains(x)) {
return;
}
items[size] = x;
size += 1;
}
/* Returns the number of key-value mappings in this map. */
public int size() {
return size;
}
}
这个程序有一个小问题:当我们向\(ArraySet\)中加入\(null\)时,会产生NullPointerException
的错误。这是因为:在contains
方法中,由于加入了\(null\),调用\(items[i]\)时会产生这种错误。
我们可以选择抛出自己的异常(\(exceptions\))。在\(python\)中我们使用\(raise\)语句,而在\(Java\)中,我们可以用如下语句声明错误:
1
throw new ExceptionObject(parameter1, ...)
于是,我们可以如下编写我们的异常抛弃(\(throwing\;exception\))语句:
1
2
3
4
5
6
7
8
9
10
11
12/* Associates the specified value with the specified key in this map.
Throws an IllegalArgumentException if the key is null. */
public void add(T x) {
if (x == null) {
throw new IllegalArgumentException("can't add null");
}
if (contains(x)) {
return;
}
items[size] = x;
size += 1;
}
虽然不管有没有\(throw\)语句,程序都会在该处中断。但是,通过\(throw\)语句,我们对程序有了更高的掌控力:我们可以自己决定程序应该在何处中断。同时,我们也可以将\(Java\)中的报错更改为更具体的语句。
2.\(catch\)语句
利用先前的\(throw\),我们可以将\(Java\)中的隐性错误(\(implicit\;exceptions\))显式化,例如:
1
2
3
4
5
6
7
8
9public static void main(String[] args) {
System.out.println("ayyy lmao");
throw new RuntimeException("For no reason.");
}
$ java Alien
ayyy lmao
Exception in thread "main" java.lang.RuntimeException: For no reason.
at Alien.main(Alien.java:4)
这里,我们发现了构造方法new
,这看起来和类的实例化非常相似——事实上就是如此。\(RuntimeException\)也是一个普通的\(Java\)实例。
因此,我们可以\(catch\)每个异常实例,如下面的例子:
1
2
3
4
5
6
7
8
9Dog d = new Dog("Lucy", "Retriever", 80);
d.becomeAngry();
try {
d.receivePat();
} catch (Exception e) {
System.out.println("Tried to pat: " + e);
}
System.out.println(d);
其对应输出可能为: 1
2
3$ java ExceptionDemo
Tried to pat: java.lang.RuntimeException: grrr... snarl snarl
Lucy is a displeased Retriever weighing 80.0 standard lb units.
可以看到,我们在\(catch\)到RuntimeException
后,依然执行到了最后一行。
我们还可以用\(catch\)语句来执行修正措施:
1
2
3
4
5
6
7
8
9
10
11
12Dog d = new Dog("Lucy", "Retriever", 80);
d.becomeAngry();
try {
d.receivePat();
} catch (Exception e) {
System.out.println(
"Tried to pat: " + e);
d.eatTreat("banana");
}
d.receivePat();
System.out.println(d);
在这个程序中,我们在发现异常时用\(treat\)来慰抚\(Dog\),这样,程序的异常就解决了:
1
2
3
4
5
6
7$ java ExceptionDemo
Tried to pat: java.lang.RuntimeException: grrr... snarl snarl
Lucy munches the banana
Lucy enjoys the pat.
Lucy is a happy Retriever weighing 80.0 standard lb units.
In the real world, this corrective action might be extending an antenna on a robot when an exception is thrown by an operation expecting a ready antenna. Or perhaps we simply want to write the error to a log file for later analysis.
3.异常处理的哲学
异常处理使得我们在概念上将错误处理(\(error\;handling\))与剩余部分的程序分离开来。例如,考虑下面的程序:
1
2
3
4
5
6
7func readFile: {
open the file;
determine its size;
allocate that much memory;
read the file into memory;
close the file;
}
如果不进行异常处理,我们可能会对程序进行如下改写:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22func readFile: {
open the file;
if (theFileIsOpen) {
determine its size;
if (gotTheFileLength) {
allocate that much memory;
} else {
return error("fileLengthError");
}
if (gotEnoughMemory) {
read the file into memory;
if (readFailed) {
return error("readError");
}
...
} else {
return error("memoryError");
}
} else {
return error("fileOpenError")
}
}
这样的代码十分冗杂。我们可以用异常处理如下书写: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19func readFile: {
try {
open the file;
determine its size;
allocate that much memory;
read the file into memory;
close the file;
} catch (fileOpenFailed) {
doSomething;
} catch (sizeDeterminationFailed) {
doSomething;
} catch (memoryAllocationFailed) {
doSomething;
} catch (readFailed) {
doSomething;
} catch (fileCloseFailed) {
doSomething;
}
}
异常处理的方法让代码十分整洁:首先,程序先尝试执行所需的操作;然后,程序开始捕捉所有的错误。
Good code feels like a story; it has a certain beauty to its construction. That clarity makes it easier to both write and maintain over time.
4.未被捕捉的异常
当一个异常被引发时,它会向下历经如下的栈:
如果三个方法都没有捕捉到异常,程序就会中断,\(Java\)会给用户发送信息,并打印堆栈跟踪的信息:
1
2
3
4java.lang.RuntimeException in thread “main”:
at ArrayRingBuffer.peek:63
at GuitarString.sample:48
at GuitarHeroLite.java:110