Appearance
异常处理
异常是程序在执行过程中发生的不正常事件,会中断正常的指令流程。Java 提供了完善的异常处理机制,帮助程序优雅地处理错误。
异常概述
异常体系
text
┌─────────────────────────────────────────────────────────────────┐
│ Java 异常体系 │
├─────────────────────────────────────────────────────────────────┤
│ Throwable │
│ / \ │
│ Error Exception │
│ / \ / \ │
│ VirtualMachineError IOException RuntimeException │
│ OutOfMemoryError SQLException NullPointerException│
│ StackOverflowError ArrayIndexOutOfBounds│
│ │
│ Error:严重错误,程序无法处理 │
│ Exception:异常,程序可以处理 │
│ - 检查异常(Checked):编译时检查,必须处理 │
│ - 运行时异常(Unchecked):运行时检查,可选处理 │
└─────────────────────────────────────────────────────────────────┘异常分类
| 类型 | 说明 | 示例 |
|---|---|---|
| Error | JVM 严重错误,程序无法恢复 | OutOfMemoryError, StackOverflowError |
| 检查异常 | 编译时检查,必须处理 | IOException, SQLException |
| 运行时异常 | 运行时检查,可选处理 | NullPointerException, ArrayIndexOutOfBoundsException |
常见异常
java
public class CommonExceptions {
public static void main(String[] args) {
// 1. NullPointerException:空指针异常
String str = null;
// System.out.println(str.length()); // NullPointerException
// 2. ArrayIndexOutOfBoundsException:数组越界
int[] arr = {1, 2, 3};
// System.out.println(arr[5]); // ArrayIndexOutOfBoundsException
// 3. ArithmeticException:算术异常
// int result = 10 / 0; // ArithmeticException
// 4. NumberFormatException:数字格式异常
// int num = Integer.parseInt("abc"); // NumberFormatException
// 5. ClassCastException:类型转换异常
Object obj = "Hello";
// Integer num = (Integer) obj; // ClassCastException
// 6. IndexOutOfBoundsException:索引越界
// "Hello".charAt(10); // IndexOutOfBoundsException
// 7. IllegalArgumentException:非法参数
// Thread.sleep(-1000); // IllegalArgumentException
}
}异常处理机制
try-catch
java
public class TryCatchDemo {
public static void main(String[] args) {
// 基本 try-catch
try {
int result = 10 / 0; // 可能抛出异常的代码
System.out.println("结果:" + result);
} catch (ArithmeticException e) {
// 捕获并处理异常
System.out.println("捕获到异常:" + e.getMessage());
}
System.out.println("程序继续执行...");
// 多个 catch 块
try {
int[] arr = {1, 2, 3};
System.out.println(arr[5]); // 数组越界
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组越界:" + e.getMessage());
} catch (NullPointerException e) {
System.out.println("空指针:" + e.getMessage());
} catch (Exception e) {
System.out.println("其他异常:" + e.getMessage());
}
// 多异常捕获(Java 7+)
try {
String str = null;
System.out.println(str.length());
} catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
System.out.println("捕获多个异常:" + e.getClass().getSimpleName());
}
}
}try-catch-finally
java
public class FinallyDemo {
public static void main(String[] args) {
// finally 块:无论是否发生异常都会执行
System.out.println("返回值:" + testFinally());
}
public static int testFinally() {
try {
System.out.println("try 块执行");
return 1;
} catch (Exception e) {
System.out.println("catch 块执行");
return 2;
} finally {
System.out.println("finally 块执行(总是会执行)");
// 注意:不要在 finally 中使用 return
// return 3; // 会覆盖 try 或 catch 的返回值
}
}
}try-with-resources
java
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class TryWithResources {
public static void main(String[] args) {
// Java 7+ try-with-resources:自动关闭资源
// 实现了 AutoCloseable 接口的资源会自动关闭
// 传统方式
FileInputStream fis = null;
try {
fis = new FileInputStream("input.txt");
int data = fis.read();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// try-with-resources 方式(推荐)
try (FileInputStream input = new FileInputStream("input.txt");
FileOutputStream output = new FileOutputStream("output.txt")) {
int data;
while ((data = input.read()) != -1) {
output.write(data);
}
} catch (IOException e) {
e.printStackTrace();
}
// 资源会自动关闭,无需 finally
}
}throw 和 throws
throw 关键字
throw 用于手动抛出异常。
java
public class ThrowDemo {
public static void main(String[] args) {
try {
validateAge(-5);
} catch (IllegalArgumentException e) {
System.out.println("异常信息:" + e.getMessage());
}
try {
divide(10, 0);
} catch (ArithmeticException e) {
System.out.println("除法异常:" + e.getMessage());
}
}
// 使用 throw 抛出异常
public static void validateAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("年龄不能为负数:" + age);
}
if (age > 150) {
throw new IllegalArgumentException("年龄不合理:" + age);
}
System.out.println("年龄有效:" + age);
}
// 抛出异常后,方法结束
public static int divide(int a, int b) {
if (b == 0) {
throw new ArithmeticException("除数不能为零");
}
return a / b;
}
}throws 关键字
throws 用于声明方法可能抛出的异常。
java
import java.io.FileInputStream;
import java.io.IOException;
public class ThrowsDemo {
public static void main(String[] args) {
// 调用声明了检查异常的方法,必须处理
try {
readFile("test.txt");
} catch (IOException e) {
System.out.println("文件读取失败:" + e.getMessage());
}
// 继续抛出异常
try {
method1();
} catch (Exception e) {
e.printStackTrace();
}
}
// 声明可能抛出的检查异常
public static void readFile(String filename) throws IOException {
FileInputStream fis = new FileInputStream(filename);
fis.close();
}
// 可以声明多个异常
public static void method1() throws IOException, ClassNotFoundException {
// 方法体
}
// 运行时异常可以不声明
public static void method2() {
throw new RuntimeException("运行时异常");
}
}throw vs throws
text
┌─────────────────────────────────────────────────────────────────┐
│ throw vs throws │
├─────────────────────────────────────────────────────────────────┤
│ throw: │
│ - 在方法体内使用 │
│ - 用于抛出异常对象 │
│ - 一次只能抛出一个异常 │
│ │
│ throws: │
│ - 在方法声明上使用 │
│ - 用于声明可能抛出的异常类型 │
│ - 可以声明多个异常类型 │
└─────────────────────────────────────────────────────────────────┘自定义异常
自定义检查异常
java
// 自定义检查异常:继承 Exception
class InsufficientBalanceException extends Exception {
private double balance;
private double amount;
public InsufficientBalanceException(double balance, double amount) {
super("余额不足:当前余额 " + balance + ",取款金额 " + amount);
this.balance = balance;
this.amount = amount;
}
public double getBalance() {
return balance;
}
public double getAmount() {
return amount;
}
}
// 银行账户类
class BankAccount {
private double balance;
public BankAccount(double balance) {
this.balance = balance;
}
// 取款方法:声明可能抛出的异常
public void withdraw(double amount) throws InsufficientBalanceException {
if (amount > balance) {
throw new InsufficientBalanceException(balance, amount);
}
balance -= amount;
System.out.println("取款成功,剩余余额:" + balance);
}
public double getBalance() {
return balance;
}
}
public class CustomExceptionDemo {
public static void main(String[] args) {
BankAccount account = new BankAccount(1000);
try {
account.withdraw(500); // 成功
account.withdraw(800); // 失败
} catch (InsufficientBalanceException e) {
System.out.println("取款失败:" + e.getMessage());
System.out.println("当前余额:" + e.getBalance());
System.out.println("取款金额:" + e.getAmount());
}
}
}自定义运行时异常
java
// 自定义运行时异常:继承 RuntimeException
class InvalidAgeException extends RuntimeException {
public InvalidAgeException(String message) {
super(message);
}
public InvalidAgeException(int age) {
super("无效年龄:" + age);
}
}
public class CustomRuntimeException {
public static void main(String[] args) {
// 运行时异常不需要强制处理
setAge(25); // 正常
setAge(-5); // 抛出异常
}
public static void setAge(int age) {
if (age < 0 || age > 150) {
throw new InvalidAgeException(age);
}
System.out.println("年龄设置为:" + age);
}
}异常链
java
public class ExceptionChain {
public static void main(String[] args) {
try {
method3();
} catch (Exception e) {
// 打印异常链
e.printStackTrace();
// 获取原始异常
Throwable cause = e.getCause();
if (cause != null) {
System.out.println("\n原始异常:" + cause.getMessage());
}
}
}
public static void method1() throws SQLException {
throw new SQLException("数据库连接失败");
}
public static void method2() throws BusinessException {
try {
method1();
} catch (SQLException e) {
// 使用原始异常创建新异常(异常链)
throw new BusinessException("业务处理失败", e);
}
}
public static void method3() throws Exception {
method2();
}
}
// 业务异常
class BusinessException extends Exception {
public BusinessException(String message) {
super(message);
}
public BusinessException(String message, Throwable cause) {
super(message, cause); // 保留原始异常
}
}异常处理最佳实践
合理选择异常类型
java
public class ExceptionBestPractice {
// 好的做法:使用合适的异常类型
// 检查异常:需要调用者处理的业务异常
public void withdraw(double amount) throws InsufficientBalanceException {
// ...
}
// 运行时异常:编程错误
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("年龄不能为负数");
}
}
// 尽早失败
public void process(String input) {
// 参数校验放在方法开头
if (input == null) {
throw new IllegalArgumentException("输入不能为空");
}
if (input.isEmpty()) {
throw new IllegalArgumentException("输入不能为空字符串");
}
// 业务逻辑
System.out.println("处理:" + input);
}
// 不要忽略异常
public void badPractice() {
try {
// 一些操作
} catch (Exception e) {
// 空 catch 块是错误的做法
// 至少应该记录日志
}
}
// 好的做法:记录日志
public void goodPractice() {
try {
// 一些操作
} catch (Exception e) {
// 记录异常信息
System.err.println("发生异常:" + e.getMessage());
e.printStackTrace();
// 或者使用日志框架
// logger.error("发生异常", e);
}
}
// 保留原始异常
public void withCause() {
try {
// 一些操作
} catch (SQLException e) {
// 保留原始异常信息
throw new RuntimeException("数据库操作失败", e);
}
}
}异常信息要详细
java
public class ExceptionMessage {
public static void main(String[] args) {
// 不好的做法:异常信息不明确
try {
throw new IllegalArgumentException("参数错误");
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage()); // 参数错误
}
// 好的做法:异常信息详细
try {
setAge(-5);
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage()); // 年龄无效:-5,年龄必须在 0-150 之间
}
}
public static void setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException(
"年龄无效:" + age + ",年龄必须在 0-150 之间"
);
}
}
}小结
本章我们学习了:
- 异常体系:Error、Exception、检查异常、运行时异常
- 异常处理:try-catch、finally、try-with-resources
- 抛出异常:throw、throws
- 自定义异常:继承 Exception 或 RuntimeException
- 异常链:保留原始异常信息
- 最佳实践:合理选择异常类型、详细异常信息、不忽略异常
下一章,我们将学习 集合框架,了解 Java 中常用的数据结构。
