原文链接:https://www.cnblogs.com/Time25/p/20025633.html

异常的传播

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
// exception
public class Main {
public static void main(String[] args) {
try {
process1();
} catch (Exception e) {
e.printStackTrace();
}
}

static void process1() {
process2();
}

static void process2() {
Integer.parseInt(null); // 会抛出NumberFormatException
}
}
当某个方法抛出了异常时,如果当前方法没有捕获异常,异常就会被抛到上层调用方法,直到遇到某个try ... catch被捕获为止:

main()调用process1();
process1()调用process2();
process2()调用Integer.parseInt(String);
Integer.parseInt(String)调用Integer.parseInt(String, int)。

也叫异常的传播

查看Integer.java源码可知,抛出异常的方法代码如下:

public static int parseInt(String s, int radix) throws NumberFormatException {
if (s == null) {
throw new NumberFormatException("null");
}
...
}

异常的抛出

1
2
创建某个Exception的实例;
用throw语句抛出。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void process2(String s) {
if (s==null) {
NullPointerException e = new NullPointerException();
throw e;
}
}


//也就是
void process2(String s) {
if (s==null) {
throw new NullPointerException();
}
}

异常的类型转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void process1(String s) {
try {
process2();
} catch (NullPointerException e) {
throw new IllegalArgumentException();
}
}

void process2(String s) {
if (s==null) {
throw new NullPointerException();
}
}

但是异常类型转换有一个问题就是,先看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// exception
//在main()中捕获IllegalArgumentException
public class Main {
public static void main(String[] args) {
try {
process1();
} catch (Exception e) {
e.printStackTrace();
}
}

static void process1() {
try {
process2();
} catch (NullPointerException e) {
throw new IllegalArgumentException();
}
}

static void process2() {
throw new NullPointerException();
}
}
//这个时候打印异常栈我们已经看不到原始异常NullPointerException的信息了。

为了能追踪到完整的异常栈,在构造异常的时候,把原始的Exception实例传进去,新的Exception就可以持有原始Exception信息。对上述代码改进如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// exception
public class Main {
public static void main(String[] args) {
try {
process1();
} catch (Exception e) {
e.printStackTrace();
}
}

static void process1() {
try {
process2();
} catch (NullPointerException e) {
throw new IllegalArgumentException(e);//传入这个e
}
}

static void process2() {
throw new NullPointerException();
}
}

捕获到异常并再次抛出时,一定要留住原始异常,否则很难定位第一案发现场!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// exception
public class Main {
public static void main(String[] args) {
try {
Integer.parseInt("abc");
} catch (Exception e) {
System.out.println("catched");
throw new RuntimeException(e);
} finally {
System.out.println("finally");
}
}
}
///依然会执行finally,jvm会先执行finally再抛出异常

小疑问:

如果在finally里面抛出的异常时前面catch可以接受的异常,那么是不是会一直循环呢?
答案是不会的,而且这样非常蠢,会让程序直接崩掉

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
36
37
38
39
40
//好比自杀的代码
public static void main(String[] args) {
try {
System.out.println("1. 执行 try");
throw new IOException(); // 抛出 A 异常
} catch (IOException e) {
System.out.println("2. 捕获到了 try 里的 IOException");
} finally {
System.out.println("3. 执行 finally");
// 故意在 finally 里又跑出一个 IOException
throw new IOException("Finally 里的异常");
}
}

//catch只会管着try里面的代码,不会管跟他并列的finally


程序的实际运行路径
执行 try:抛出了 IOException。
执行 catch:匹配成功,打印 "捕获到了..."。
执行 finally:打印 "执行 finally"。
finally 抛出异常:重点来了!
这个异常发生在 finally 块里。
当前的 catch 块已经执行过了,或者说它已经完成了它的使命。
Java 不会“回头”再去检查刚才那个 catch 能不能抓到这个新异常。
结果:这个异常会直接冲破整个 try-catch-finally 结构,抛给给调用它的上层(在上面的例子中,就是抛给 JVM,导致程序崩溃并打印堆栈信息)。

如果一定要在finally里面先抛出一个异常,那么就是再套一层try catch
try {
try {
// ... 原本的代码 ...
} catch (IOException e) {
// ...
} finally {
throw new IOException(); // 这个异常会抛向外层
}
}
catch (IOException e) {
System.out.println("这里才能抓到 finally 里的异常");
}

异常屏蔽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// exception
public class Main {
public static void main(String[] args) {
try {
Integer.parseInt("abc");
} catch (Exception e) {
System.out.println("catched");
throw new RuntimeException(e);
} finally {
System.out.println("finally");
throw new IllegalArgumentException();
}
}
}
//在finally中抛出异常之后,前面抛出的异常就会被忽视掉,这个就是异常屏蔽

完整异常显示

//在极少数的情况下,我们需要获知所有的异常。如何保存所有的异常信息?方法是先用origin变量保存原始异常,然后调用Throwable.addSuppressed(),把原始异常添加进来,最后在finally抛出:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// exception
public class Main {
public static void main(String[] args) throws Exception {
Exception origin = null;
try {
System.out.println(Integer.parseInt("abc"));
} catch (Exception e) {
origin = e;
throw e;
} finally {
Exception e = new IllegalArgumentException();
if (origin != null) {
e.addSuppressed(origin);
}
throw e;
}
}
}
///这个是运行之后可以直接在黑框框上显示异常信息,这里面是自动调用了 printStackTrace(),但是你不可以使用它,你只能看到它,如果想要在代码中使用它,就要用getSuppressed()
public class Main {
public static void main(String[] args) {
try {
// 这里运行你刚才那段会抛出“嵌套异常”的代码
runDangerousCode();
} catch (Exception e) {
// 1. 打印主异常(finally里抛出的那个)
System.out.println("捕获到了主异常: " + e.getClass().getName());

// 2. 使用 getSuppressed() 获取所有被屏蔽的“附件”
Throwable[] suppressed = e.getSuppressed();
//getSuppressed() 返回的是一个数组(Throwable[]),里面装满了所有被屏蔽的异常。
for (Throwable s : suppressed) {
// 3. 打印出那些被藏起来的异常(try里抛出的那个)
System.out.println("发现被屏蔽的附件异常: " + s);
}
}
}

// 这是刚才那段逻辑
public static void runDangerousCode() throws Exception {
Exception origin = null;
try {
Integer.parseInt("abc"); // 抛出 NumberFormatException
} catch (Exception e) {
origin = e;
throw e;
} finally {
Exception e = new IllegalArgumentException(); // 主异常
if (origin != null) {
e.addSuppressed(origin); // 把刚才的报错塞进兜里
}
throw e;
}
}
}

// TRAINING LOG · 第 65 / 76 篇训练记录 · 成文于 2026.05.12 · 夜间 19:11