Java 异常处理
异常层次、try-catch-finally、多重捕获、throw/throws 与自定义异常。
Exception 类的层次
Throwable splits into Error (JVM-level, should not be caught) and Exception. Exception further splits into checked exceptions (compiler forces handling) and RuntimeException (unchecked).
public class Hierarchy {
public static void main(String[] args) {
Object e = new ArithmeticException("x");
System.out.println(e instanceof Throwable);
System.out.println(e instanceof Exception);
System.out.println(e instanceof RuntimeException);
System.out.println(e instanceof Error);
}
}true
true
true
falseJava 内置异常类
Common unchecked: NullPointerException, ArrayIndexOutOfBoundsException, ArithmeticException, ClassCastException, NumberFormatException. Checked: IOException, SQLException.
| Exception | Typical cause |
|---|---|
| NullPointerException | Calling a method on null |
| ArrayIndexOutOfBounds | Index < 0 or >= length |
| ArithmeticException | Integer division by zero |
| NumberFormatException | Parsing non-numeric text |
| ClassCastException | Invalid downcast |
| IOException | File or network failure (checked) |
捕获异常
try wraps risky code and catch handles by type. finally always runs and is meant for cleanup, though try-with-resources is now preferred.
public class CatchDemo {
public static void main(String[] args) {
try {
int[] a = new int[2];
System.out.println(a[5]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("caught: " + e.getClass().getSimpleName());
} finally {
System.out.println("finally runs");
}
try {
System.out.println(1 / 0);
} catch (ArithmeticException e) {
System.out.println(e.getMessage());
}
}
}caught: ArrayIndexOutOfBoundsException
finally runs
/ by zero多重捕获块
A try may have several catch blocks matched top-down, so subclasses must precede their parents; otherwise the parent swallows everything and the compiler rejects the unreachable branch.
public class MultiCatch {
public static void main(String[] args) {
String[] inputs = {"42", "abc", null};
for (String s : inputs) {
try {
System.out.println(Integer.parseInt(s.trim()));
} catch (NumberFormatException e) {
System.out.println("not a number: " + s);
} catch (NullPointerException e) {
System.out.println("null input");
}
}
}
}42
not a number: abc
null inputInteger.parseInt(null) throws NumberFormatException (Cannot parse null string), not an NPE. The example calls s.trim() first, so a null element blows up on dereference and both branches are reachable.多异常合并捕获
Since Java 7, catch (A | B e) merges exceptions sharing the same handling, cutting duplication. The variable is typed as their common parent, so only parent methods are available.
public class UnionCatch {
public static void main(String[] args) {
try {
String s = args.length > 0 ? args[0] : null;
System.out.println(s.length());
} catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
System.out.println("handled: " + e.getClass().getSimpleName());
}
}
}handled: NullPointerException抛出异常
throw raises an exception instance from code; throws on the signature declares what a method may propagate so callers handle it. Unhandled checked exceptions do not compile.
public class ThrowDemo {
static int div(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("b must not be zero");
}
return a / b;
}
static void risky() throws Exception {
throw new Exception("checked");
}
public static void main(String[] args) throws Exception {
try {
div(1, 0);
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
try {
risky();
} catch (Exception e) {
System.out.println("caught: " + e.getMessage());
}
}
}b must not be zero
caught: checked用户自定义异常
Extend Exception for a checked custom exception, RuntimeException for an unchecked one. Name it ...Exception and provide constructors taking a message and a cause.
class InsufficientBalanceException extends Exception {
private final double amount;
public InsufficientBalanceException(double amount) {
super("insufficient balance, need " + amount);
this.amount = amount;
}
public double getAmount() {
return amount;
}
}
public class CustomException {
static void withdraw(double balance, double amount) throws InsufficientBalanceException {
if (amount > balance) {
throw new InsufficientBalanceException(amount - balance);
}
System.out.println("ok");
}
public static void main(String[] args) {
try {
withdraw(100, 150);
} catch (InsufficientBalanceException e) {
System.out.println(e.getMessage());
System.out.println(e.getAmount());
}
}
}insufficient balance, need 50.0
50.0定义清理行为
finally runs on normal exit, on exception and even across return — the classic place to release resources. Beware that a return inside finally overrides the one from try.
public class FinallyDemo {
static String f() {
try {
return "from try";
} finally {
System.out.println("finally runs");
}
}
public static void main(String[] args) {
System.out.println(f());
try {
throw new RuntimeException("boom");
} catch (RuntimeException e) {
System.out.println("caught " + e.getMessage());
} finally {
System.out.println("cleanup");
}
}
}finally runs
from try
caught boom
cleanup预定义的清理行为
Objects implementing AutoCloseable can be declared in try (...) and are closed automatically at block end, in reverse declaration order — the recommended way to manage resources.
class Res implements AutoCloseable {
private final String name;
Res(String name) {
this.name = name;
System.out.println("open " + name);
}
@Override
public void close() {
System.out.println("close " + name);
}
}
public class TryResource {
public static void main(String[] args) {
try (Res a = new Res("A"); Res b = new Res("B")) {
System.out.println("working");
}
}
}open A
open B
working
close B
close AException 类的层次
顶层是 Throwable,下分 Error(JVM 级严重问题,不该捕获)和 Exception。Exception 再分受检异常(编译期强制处理)和 RuntimeException(非受检)。
public class Hierarchy {
public static void main(String[] args) {
Object e = new ArithmeticException("x");
System.out.println(e instanceof Throwable);
System.out.println(e instanceof Exception);
System.out.println(e instanceof RuntimeException);
System.out.println(e instanceof Error);
}
}true
true
true
falseJava 内置异常类
常见非受检异常:NullPointerException、ArrayIndexOutOfBoundsException、ArithmeticException、ClassCastException、NumberFormatException。受检异常:IOException、SQLException。
| 异常 | 典型原因 |
|---|---|
| NullPointerException | 对 null 调用方法 |
| ArrayIndexOutOfBounds | 下标越界 |
| ArithmeticException | 整数除以零 |
| NumberFormatException | 解析非数字文本 |
| ClassCastException | 非法的向下转型 |
| IOException | 文件或网络失败(受检) |
捕获异常
try 包住可能出错的代码,catch 按类型捕获。finally 无论是否异常都会执行,用来释放资源;现代写法更推荐 try-with-resources。
public class CatchDemo {
public static void main(String[] args) {
try {
int[] a = new int[2];
System.out.println(a[5]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("caught: " + e.getClass().getSimpleName());
} finally {
System.out.println("finally runs");
}
try {
System.out.println(1 / 0);
} catch (ArithmeticException e) {
System.out.println(e.getMessage());
}
}
}caught: ArrayIndexOutOfBoundsException
finally runs
/ by zero多重捕获块
一个 try 可以跟多个 catch,从上到下匹配,所以子类必须写在父类前面,否则父类会先接住、后面的分支永远不执行(编译器会直接报错)。
public class MultiCatch {
public static void main(String[] args) {
String[] inputs = {"42", "abc", null};
for (String s : inputs) {
try {
System.out.println(Integer.parseInt(s.trim()));
} catch (NumberFormatException e) {
System.out.println("not a number: " + s);
} catch (NullPointerException e) {
System.out.println("null input");
}
}
}
}42
not a number: abc
null inputInteger.parseInt(null) 抛的是 NumberFormatException(消息为 Cannot parse null string),不是 NPE;所以示例先调 s.trim(),元素为 null 时在解引用这一步就抛 NullPointerException,两个分支才都走得到。多异常合并捕获
Java 7 起可以用 catch (A | B e) 把处理逻辑相同的多个异常合并,减少重复代码。此时 e 的静态类型是它们的共同父类,只能调用父类方法。
public class UnionCatch {
public static void main(String[] args) {
try {
String s = args.length > 0 ? args[0] : null;
System.out.println(s.length());
} catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
System.out.println("handled: " + e.getClass().getSimpleName());
}
}
}handled: NullPointerException抛出异常
throw 在代码里主动抛出一个异常实例;throws 写在方法签名上,声明「这个方法可能抛这些异常,调用方自己处理」。受检异常不处理就编译不过。
public class ThrowDemo {
static int div(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("b must not be zero");
}
return a / b;
}
static void risky() throws Exception {
throw new Exception("checked");
}
public static void main(String[] args) throws Exception {
try {
div(1, 0);
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
try {
risky();
} catch (Exception e) {
System.out.println("caught: " + e.getMessage());
}
}
}b must not be zero
caught: checked用户自定义异常
继承 Exception 得到受检异常,继承 RuntimeException 得到非受检异常。类名以 Exception 结尾,通常提供带 message 和带 cause 的构造方法。
class InsufficientBalanceException extends Exception {
private final double amount;
public InsufficientBalanceException(double amount) {
super("insufficient balance, need " + amount);
this.amount = amount;
}
public double getAmount() {
return amount;
}
}
public class CustomException {
static void withdraw(double balance, double amount) throws InsufficientBalanceException {
if (amount > balance) {
throw new InsufficientBalanceException(amount - balance);
}
System.out.println("ok");
}
public static void main(String[] args) {
try {
withdraw(100, 150);
} catch (InsufficientBalanceException e) {
System.out.println(e.getMessage());
System.out.println(e.getAmount());
}
}
}insufficient balance, need 50.0
50.0定义清理行为
finally 无论正常结束、异常还是 return 都会执行,是释放资源的传统做法。注意 finally 里写 return 会覆盖 try 的返回值,是常见的坑。
public class FinallyDemo {
static String f() {
try {
return "from try";
} finally {
System.out.println("finally runs");
}
}
public static void main(String[] args) {
System.out.println(f());
try {
throw new RuntimeException("boom");
} catch (RuntimeException e) {
System.out.println("caught " + e.getMessage());
} finally {
System.out.println("cleanup");
}
}
}finally runs
from try
caught boom
cleanup预定义的清理行为
实现了 AutoCloseable 的对象可以放进 try (...),块结束自动 close(),多个资源按声明的逆序关闭。这是现在最推荐的资源管理方式。
class Res implements AutoCloseable {
private final String name;
Res(String name) {
this.name = name;
System.out.println("open " + name);
}
@Override
public void close() {
System.out.println("close " + name);
}
}
public class TryResource {
public static void main(String[] args) {
try (Res a = new Res("A"); Res b = new Res("B")) {
System.out.println("working");
}
}
}open A
open B
working
close B
close A