抛出异常
约 961 字大约 3 分钟
2025-07-15
3.1 异常的传播
当一个方法抛出异常,而当前方法没有进行捕获处理时,该异常会沿着方法的调用栈向上传播,直到遇到能够处理该异常的 try ... catch
块。
// 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
}
}
上述代码展示了异常的传播过程。process2()
方法中调用 Integer.parseInt(null)
会抛出一个 NumberFormatException
,由于 process2()
和 process1()
都没有捕获这个异常,因此它会一直传播到 main()
方法的 try ... catch
块中被捕获。
printStackTrace()
方法可以打印出异常的调用栈信息,这对于调试错误来说非常有用。例如:
java.lang.NumberFormatException: null
at java.base/java.lang.Integer.parseInt(Integer.java:614)
at java.base/java.lang.Integer.parseInt(Integer.java:770)
at Main.process2(Main.java:16)
at Main.process1(Main.java:12)
at Main.main(Main.java:5)
这段信息显示了异常抛出的具体位置和调用链,从下往上看,可以清晰地看到 main()
方法调用 process1()
,process1()
调用 process2()
,最后 process2()
调用 Integer.parseInt(String)
抛出了异常。
3.2 抛出异常
在开发过程中,当程序遇到错误或不符合预期的情况时,我们可以手动抛出异常。抛出异常通常包含以下两个步骤:
- 创建
Exception
类的实例。 - 使用
throw
语句抛出该实例。
例如:
void process2(String s) {
if (s==null) {
NullPointerException e = new NullPointerException();
throw e;
}
}
上述代码可以简化为:
void process2(String s) {
if (s==null) {
throw new NullPointerException();
}
}
当一个方法捕获了某个异常后,又在 catch
子句中抛出新的异常,这相当于把抛出的异常类型进行了转换:
void process1(String s) {
try {
process2();
} catch (NullPointerException e) {
throw new IllegalArgumentException();
}
}
void process2(String s) {
if (s==null) {
throw new NullPointerException();
}
}
java.lang.IllegalArgumentException
at Main.process1(Main.java:15)
at Main.main(Main.java:5)
然而,直接抛出新的异常会丢失原始异常的信息。为了保留完整的异常栈信息,在构造新的异常时,应该将原始的 Exception
实例传递进去。
// 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);
}
}
static void process2() {
throw new NullPointerException();
}
}
java.lang.IllegalArgumentException: java.lang.NullPointerException
at Main.process1(Main.java:15)
at Main.main(Main.java:5)
Caused by: java.lang.NullPointerException
at Main.process2(Main.java:20)
at Main.process1(Main.java:13)
... 1 more
通过这种方式,我们可以通过 Throwable.getCause()
方法获取原始异常,从而追踪到问题的根源。
3.3 finally
语句块
如果在 try
或者 catch
语句块中抛出异常,finally
语句块仍然会被执行。例如:
// 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");
}
}
}
上述代码的执行结果会先打印 "catched",然后打印 "finally",最后抛出 RuntimeException
异常。这表明 JVM 会先执行 finally
语句块,然后再抛出异常。
3.4 异常屏蔽
如果在执行 finally
语句时抛出异常,那么 catch
语句块中准备抛出的异常将会被屏蔽,因为 Java 只能抛出一个异常。这种现象被称为“异常屏蔽”(Suppressed Exception)。
// 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();
}
}
}
在极少数情况下,我们需要获知所有的异常信息。为了保存所有异常信息,可以先用一个变量保存原始异常,然后调用 Throwable.addSuppressed()
方法将原始异常添加到新异常中,最后在 finally
语句块中抛出新异常。
// 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;
}
}
}
通过 Throwable.getSuppressed()
方法可以获取所有的 Suppressed Exception
。但在大多数情况下,我们不需要关心 Suppressed Exception
,并且应该避免在 finally
语句块中抛出异常。