讲解
程序总会遇到意外:文件不存在、网络断开、数字格式不对、数组越界。Java 用「异常」机制把「正常流程」和「出错处理」分开:出错时抛出(throw)一个异常对象,沿调用栈向上寻找愿意处理它的 try...catch,没人接就终止程序并打印调用栈。相比「每个方法返回错误码、层层判断」的老办法,异常让正常代码保持清爽,错误处理集中到该处理的地方。
异常分两大阵营。受检异常(checked exception,如 IOException):编译器强制你处理,方法可能抛出就必须用 throws 声明或用 try...catch 接住,躲不掉;非受检异常(unchecked,RuntimeException 一族,如 NullPointerException、ArrayIndexOutOfBoundsException):编译器不强制,它们大多代表程序 bug,正确应对是修好代码而不是到处 catch。捕获的写法:try { 可能出错的代码 } catch (具体异常 e) { 处理 } finally { 无论成败都执行的清理 };catch 可以写多个,子类异常要放前面。
主动抛出异常用 throw new IllegalArgumentException("说明"),配合方法开头的参数校验(「快速失败」)能让 bug 尽早暴露。finally 块适合做资源清理,但操作文件、网络连接这类资源时更推荐 try-with-resources 语法:把资源声明在 try 后面的括号里,用完自动关闭,连 finally 都不用写。自定义异常只需继承 Exception(受检)或 RuntimeException(非受检),给业务错误一个有意义的名字。
示例
捕获并处理异常,finally 无论如何都会执行:
public class Main {
public static int divide(int a, int b) {
return a / b;
}
public static void main(String[] args) {
try {
int result = divide(10, 0);
System.out.println("结果:" + result);
} catch (ArithmeticException e) {
System.out.println("捕获异常:" + e.getMessage());
} finally {
System.out.println("finally:无论是否出错都会执行");
}
try {
int n = Integer.parseInt("abc");
System.out.println(n);
} catch (NumberFormatException e) {
System.out.println("「abc」不是合法数字,按 0 处理");
}
System.out.println("程序继续运行");
}
}
自定义受检异常、throw 抛出、throws 声明:
class InvalidAgeException extends Exception {
InvalidAgeException(String message) {
super(message);
}
}
public class Main {
public static void register(String name, int age) throws InvalidAgeException {
if (age < 0 || age > 150) {
throw new InvalidAgeException("年龄 " + age + " 不合法");
}
System.out.println(name + "(" + age + " 岁)注册成功");
}
public static void main(String[] args) {
try {
register("张三", 25);
register("李四", -3);
} catch (InvalidAgeException e) {
System.out.println("注册失败:" + e.getMessage());
}
}
}
try-with-resources:资源用完自动关闭:
import java.io.BufferedReader;
import java.io.StringReader;
public class Main {
public static void main(String[] args) throws Exception {
try (BufferedReader reader = new BufferedReader(new StringReader("第一行\n第二行"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("读到:" + line);
}
}
System.out.println("读取结束,资源已自动关闭");
}
}
常见坑
- catch 后什么都不做:空 catch 块把异常吞掉,出错时程序静悄悄地跑偏,是排查噩梦。最低限度也要打印异常和堆栈。
- 一把抓 catch (Exception e):范围太大的捕获会掩盖你没想到的错误类型。尽量捕获具体异常;多个 catch 时子类在前、父类在后,否则前面的父类会让后面的子类永远不可达(编译报错)。
- 用异常控制正常流程:拿 NumberFormatException 当「判断字符串是不是数字」的工具,既慢又丑。异常是处理意外的,常规分支用 if。
- 把受检异常一路 throws 到 main:等于谁都没处理。异常应该在「有能力妥善处理它的那一层」被 catch,或者转换成有意义的业务异常继续抛。
小结
异常把错误处理从正常流程中分离;受检异常强制处理,非受检异常修代码优先;throw 主动抛出,try-with-resources 自动关资源。下一节进入日常开发使用频率最高的领域:集合框架 List。