异常处理是Java程序设计中的重要概念,它提供了一种优雅地处理程序运行时错误的机制。本章将详细介绍Java异常处理的概念、语法和最佳实践。
6.1 异常基础概念
6.1.1 什么是异常
/**
* 异常演示类 - 展示常见的异常情况
*/
public class ExceptionDemo {
public static void main(String[] args) {
System.out.println("=== 常见异常演示 ===");
// 1. 算术异常 (ArithmeticException)
demonstrateArithmeticException();
// 2. 空指针异常 (NullPointerException)
demonstrateNullPointerException();
// 3. 数组越界异常 (ArrayIndexOutOfBoundsException)
demonstrateArrayIndexOutOfBoundsException();
// 4. 类型转换异常 (ClassCastException)
demonstrateClassCastException();
// 5. 数字格式异常 (NumberFormatException)
demonstrateNumberFormatException();
}
public static void demonstrateArithmeticException() {
System.out.println("\n--- 算术异常演示 ---");
try {
int a = 10;
int b = 0;
int result = a / b; // 除零异常
System.out.println("结果: " + result);
} catch (ArithmeticException e) {
System.out.println("捕获到算术异常: " + e.getMessage());
System.out.println("异常类型: " + e.getClass().getSimpleName());
}
}
public static void demonstrateNullPointerException() {
System.out.println("\n--- 空指针异常演示 ---");
try {
String str = null;
int length = str.length(); // 空指针异常
System.out.println("字符串长度: " + length);
} catch (NullPointerException e) {
System.out.println("捕获到空指针异常: " + e.getMessage());
System.out.println("建议: 在使用对象前检查是否为null");
}
}
public static void demonstrateArrayIndexOutOfBoundsException() {
System.out.println("\n--- 数组越界异常演示 ---");
try {
int[] array = {1, 2, 3, 4, 5};
int value = array[10]; // 数组越界
System.out.println("数组值: " + value);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("捕获到数组越界异常: " + e.getMessage());
System.out.println("数组长度检查很重要!");
}
}
public static void demonstrateClassCastException() {
System.out.println("\n--- 类型转换异常演示 ---");
try {
Object obj = "这是一个字符串";
Integer number = (Integer) obj; // 类型转换异常
System.out.println("转换后的数字: " + number);
} catch (ClassCastException e) {
System.out.println("捕获到类型转换异常: " + e.getMessage());
System.out.println("建议: 使用instanceof检查类型");
}
}
public static void demonstrateNumberFormatException() {
System.out.println("\n--- 数字格式异常演示 ---");
try {
String invalidNumber = "abc123";
int number = Integer.parseInt(invalidNumber); // 数字格式异常
System.out.println("解析后的数字: " + number);
} catch (NumberFormatException e) {
System.out.println("捕获到数字格式异常: " + e.getMessage());
System.out.println("无法将 '" + "abc123" + "' 转换为数字");
}
}
}
6.1.2 异常的层次结构
/**
* 异常层次结构演示
*/
public class ExceptionHierarchyDemo {
public static void main(String[] args) {
System.out.println("=== Java异常层次结构 ===");
// Throwable
// ├── Error (系统级错误,通常不处理)
// │ ├── OutOfMemoryError
// │ ├── StackOverflowError
// │ └── VirtualMachineError
// └── Exception (可处理的异常)
// ├── RuntimeException (运行时异常,非检查异常)
// │ ├── NullPointerException
// │ ├── ArrayIndexOutOfBoundsException
// │ ├── ClassCastException
// │ └── IllegalArgumentException
// └── 检查异常 (Checked Exceptions)
// ├── IOException
// ├── SQLException
// ├── ClassNotFoundException
// └── ParseException
demonstrateExceptionTypes();
}
public static void demonstrateExceptionTypes() {
System.out.println("\n=== 异常类型演示 ===");
// 1. 运行时异常(非检查异常)
System.out.println("\n1. 运行时异常(RuntimeException):");
try {
// 这些异常在编译时不需要强制处理
throwRuntimeException();
} catch (RuntimeException e) {
System.out.println("捕获运行时异常: " + e.getClass().getSimpleName());
System.out.println("消息: " + e.getMessage());
}
// 2. 检查异常
System.out.println("\n2. 检查异常(Checked Exception):");
try {
// 这些异常在编译时必须处理
throwCheckedException();
} catch (Exception e) {
System.out.println("捕获检查异常: " + e.getClass().getSimpleName());
System.out.println("消息: " + e.getMessage());
}
// 3. 错误(Error)
System.out.println("\n3. 错误(Error):");
System.out.println("Error通常表示严重的系统级问题,一般不应该被捕获");
System.out.println("例如: OutOfMemoryError, StackOverflowError");
}
public static void throwRuntimeException() {
// 运行时异常示例
throw new IllegalArgumentException("这是一个非法参数异常");
}
public static void throwCheckedException() throws Exception {
// 检查异常示例
throw new Exception("这是一个检查异常");
}
}
6.2 异常处理语法
6.2.1 try-catch语句
import java.io.*;
import java.util.Scanner;
/**
* try-catch语句详细演示
*/
public class TryCatchDemo {
public static void main(String[] args) {
System.out.println("=== try-catch语句演示 ===");
// 1. 基本try-catch
basicTryCatch();
// 2. 多个catch块
multipleCatchBlocks();
// 3. catch块的顺序
catchBlockOrder();
// 4. try-catch-finally
tryCatchFinally();
// 5. try-with-resources
tryWithResources();
}
public static void basicTryCatch() {
System.out.println("\n--- 基本try-catch ---");
try {
System.out.println("尝试执行可能出错的代码");
int result = 10 / 0; // 这里会抛出异常
System.out.println("这行代码不会执行");
} catch (ArithmeticException e) {
System.out.println("捕获到异常,进行处理");
System.out.println("异常信息: " + e.getMessage());
}
System.out.println("程序继续执行");
}
public static void multipleCatchBlocks() {
System.out.println("\n--- 多个catch块 ---");
String[] testCases = {null, "abc", "5"};
for (int i = 0; i < testCases.length; i++) {
try {
System.out.println("\n测试用例 " + (i + 1) + ": " + testCases[i]);
// 可能抛出NullPointerException
int length = testCases[i].length();
System.out.println("字符串长度: " + length);
// 可能抛出NumberFormatException
int number = Integer.parseInt(testCases[i]);
System.out.println("解析的数字: " + number);
// 可能抛出ArithmeticException
int result = 100 / number;
System.out.println("计算结果: " + result);
} catch (NullPointerException e) {
System.out.println("处理空指针异常: 字符串为null");
} catch (NumberFormatException e) {
System.out.println("处理数字格式异常: 无法解析为数字");
} catch (ArithmeticException e) {
System.out.println("处理算术异常: 除零错误");
}
}
}
public static void catchBlockOrder() {
System.out.println("\n--- catch块的顺序 ---");
try {
// 模拟抛出不同类型的异常
throw new IllegalArgumentException("非法参数异常");
} catch (IllegalArgumentException e) {
// 具体异常类型应该放在前面
System.out.println("捕获具体异常: " + e.getClass().getSimpleName());
System.out.println("消息: " + e.getMessage());
} catch (RuntimeException e) {
// 父类异常放在后面
System.out.println("捕获运行时异常: " + e.getClass().getSimpleName());
} catch (Exception e) {
// 最通用的异常放在最后
System.out.println("捕获通用异常: " + e.getClass().getSimpleName());
}
System.out.println("\n注意: catch块的顺序很重要!");
System.out.println("- 子类异常必须在父类异常之前");
System.out.println("- 否则会导致编译错误");
}
public static void tryCatchFinally() {
System.out.println("\n--- try-catch-finally ---");
FileInputStream fis = null;
try {
System.out.println("尝试打开文件");
fis = new FileInputStream("nonexistent.txt");
System.out.println("文件打开成功");
} catch (FileNotFoundException e) {
System.out.println("文件未找到: " + e.getMessage());
} finally {
// finally块总是会执行
System.out.println("执行清理工作");
if (fis != null) {
try {
fis.close();
System.out.println("文件流已关闭");
} catch (IOException e) {
System.out.println("关闭文件流时出错: " + e.getMessage());
}
}
}
System.out.println("\nfinally块的特点:");
System.out.println("- 无论是否发生异常都会执行");
System.out.println("- 常用于资源清理");
System.out.println("- 即使在try或catch中有return语句,finally也会执行");
}
public static void tryWithResources() {
System.out.println("\n--- try-with-resources ---");
// Java 7引入的语法,自动管理资源
try (Scanner scanner = new Scanner(System.in);
FileWriter writer = new FileWriter("temp.txt")) {
System.out.println("使用try-with-resources自动管理资源");
writer.write("这是测试内容");
System.out.println("文件写入完成");
} catch (IOException e) {
System.out.println("IO异常: " + e.getMessage());
}
// 资源会自动关闭,无需手动调用close()
System.out.println("\ntry-with-resources的优点:");
System.out.println("- 自动关闭实现了AutoCloseable接口的资源");
System.out.println("- 代码更简洁,减少资源泄漏风险");
System.out.println("- 即使发生异常,资源也会被正确关闭");
}
}
6.2.2 throws和throw关键字
import java.io.*;
import java.util.Date;
/**
* throws和throw关键字演示
*/
public class ThrowsAndThrowDemo {
public static void main(String[] args) {
System.out.println("=== throws和throw关键字演示 ===");
// 1. throws关键字演示
demonstrateThrows();
// 2. throw关键字演示
demonstrateThrow();
// 3. 异常传播演示
demonstrateExceptionPropagation();
}
public static void demonstrateThrows() {
System.out.println("\n--- throws关键字演示 ---");
try {
// 调用声明抛出异常的方法
readFile("test.txt");
} catch (IOException e) {
System.out.println("处理IO异常: " + e.getMessage());
}
try {
parseNumber("abc");
} catch (NumberFormatException e) {
System.out.println("处理数字格式异常: " + e.getMessage());
}
}
// 使用throws声明方法可能抛出的检查异常
public static void readFile(String filename) throws IOException {
System.out.println("尝试读取文件: " + filename);
// 这个方法可能抛出IOException
FileReader reader = new FileReader(filename);
// 如果到达这里,说明文件存在
System.out.println("文件读取成功");
reader.close();
}
// throws可以声明多个异常
public static void parseNumber(String str) throws NumberFormatException, IllegalArgumentException {
if (str == null) {
throw new IllegalArgumentException("输入字符串不能为null");
}
// 这里可能抛出NumberFormatException
int number = Integer.parseInt(str);
System.out.println("解析成功: " + number);
}
public static void demonstrateThrow() {
System.out.println("\n--- throw关键字演示 ---");
try {
validateAge(-5);
} catch (IllegalArgumentException e) {
System.out.println("年龄验证失败: " + e.getMessage());
}
try {
validateEmail("invalid-email");
} 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("年龄不能超过150岁: " + age);
}
System.out.println("年龄验证通过: " + age);
}
public static void validateEmail(String email) {
if (email == null || email.trim().isEmpty()) {
throw new IllegalArgumentException("邮箱地址不能为空");
}
if (!email.contains("@")) {
throw new IllegalArgumentException("邮箱地址格式不正确: " + email);
}
System.out.println("邮箱验证通过: " + email);
}
public static double divide(double a, double b) {
if (b == 0) {
throw new ArithmeticException("除数不能为零");
}
return a / b;
}
public static void demonstrateExceptionPropagation() {
System.out.println("\n--- 异常传播演示 ---");
try {
methodA();
} catch (Exception e) {
System.out.println("在main方法中捕获异常: " + e.getMessage());
System.out.println("异常传播路径:");
// 打印异常堆栈跟踪
StackTraceElement[] stackTrace = e.getStackTrace();
for (int i = 0; i < Math.min(stackTrace.length, 5); i++) {
StackTraceElement element = stackTrace[i];
System.out.println(" " + element.getMethodName() + "() 在 " +
element.getFileName() + ":" + element.getLineNumber());
}
}
}
public static void methodA() throws Exception {
System.out.println("进入methodA");
methodB();
System.out.println("这行代码不会执行");
}
public static void methodB() throws Exception {
System.out.println("进入methodB");
methodC();
System.out.println("这行代码不会执行");
}
public static void methodC() throws Exception {
System.out.println("进入methodC");
throw new Exception("在methodC中抛出的异常");
}
}
6.3 自定义异常
6.3.1 创建自定义异常类
/**
* 银行业务异常 - 自定义检查异常
*/
public class BankException extends Exception {
private String errorCode;
private double amount;
public BankException(String message) {
super(message);
}
public BankException(String message, String errorCode) {
super(message);
this.errorCode = errorCode;
}
public BankException(String message, String errorCode, double amount) {
super(message);
this.errorCode = errorCode;
this.amount = amount;
}
public BankException(String message, Throwable cause) {
super(message, cause);
}
public BankException(String message, String errorCode, Throwable cause) {
super(message, cause);
this.errorCode = errorCode;
}
public String getErrorCode() {
return errorCode;
}
public double getAmount() {
return amount;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName()).append(": ");
sb.append(getMessage());
if (errorCode != null) {
sb.append(" [错误代码: ").append(errorCode).append("]");
}
if (amount != 0) {
sb.append(" [涉及金额: ").append(amount).append("]");
}
return sb.toString();
}
}
/**
* 余额不足异常 - 继承自BankException
*/
public class InsufficientFundsException extends BankException {
private double currentBalance;
private double requestedAmount;
public InsufficientFundsException(double currentBalance, double requestedAmount) {
super(String.format("余额不足:当前余额 %.2f,请求金额 %.2f",
currentBalance, requestedAmount),
"INSUFFICIENT_FUNDS", requestedAmount);
this.currentBalance = currentBalance;
this.requestedAmount = requestedAmount;
}
public double getCurrentBalance() {
return currentBalance;
}
public double getRequestedAmount() {
return requestedAmount;
}
public double getShortfall() {
return requestedAmount - currentBalance;
}
}
/**
* 账户锁定异常 - 自定义运行时异常
*/
public class AccountLockedException extends RuntimeException {
private String accountNumber;
private String lockReason;
private Date lockTime;
public AccountLockedException(String accountNumber, String lockReason) {
super("账户已锁定:" + accountNumber + ",原因:" + lockReason);
this.accountNumber = accountNumber;
this.lockReason = lockReason;
this.lockTime = new Date();
}
public String getAccountNumber() {
return accountNumber;
}
public String getLockReason() {
return lockReason;
}
public Date getLockTime() {
return lockTime;
}
}
/**
* 无效交易异常
*/
public class InvalidTransactionException extends BankException {
public enum TransactionType {
DEPOSIT("存款"),
WITHDRAWAL("取款"),
TRANSFER("转账"),
PAYMENT("支付");
private final String description;
TransactionType(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
private TransactionType transactionType;
private String transactionId;
public InvalidTransactionException(String message, TransactionType type, String transactionId) {
super(message, "INVALID_TRANSACTION");
this.transactionType = type;
this.transactionId = transactionId;
}
public TransactionType getTransactionType() {
return transactionType;
}
public String getTransactionId() {
return transactionId;
}
@Override
public String toString() {
return super.toString() +
" [交易类型: " + transactionType.getDescription() +
", 交易ID: " + transactionId + "]";
}
}
6.3.2 使用自定义异常
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* 银行账户类 - 使用自定义异常
*/
public class BankAccount {
private String accountNumber;
private String ownerName;
private double balance;
private boolean isLocked;
private int failedAttempts;
private static final int MAX_FAILED_ATTEMPTS = 3;
// 交易历史
private Map<String, Transaction> transactionHistory;
public BankAccount(String accountNumber, String ownerName, double initialBalance) {
this.accountNumber = accountNumber;
this.ownerName = ownerName;
this.balance = initialBalance;
this.isLocked = false;
this.failedAttempts = 0;
this.transactionHistory = new HashMap<>();
}
// 存款
public void deposit(double amount) throws InvalidTransactionException {
String transactionId = generateTransactionId();
try {
validateAccount();
validateAmount(amount, "存款金额必须大于0");
balance += amount;
recordTransaction(transactionId, InvalidTransactionException.TransactionType.DEPOSIT, amount);
System.out.println(String.format("存款成功:%.2f,当前余额:%.2f", amount, balance));
} catch (Exception e) {
throw new InvalidTransactionException(
"存款失败:" + e.getMessage(),
InvalidTransactionException.TransactionType.DEPOSIT,
transactionId
);
}
}
// 取款
public void withdraw(double amount) throws BankException {
String transactionId = generateTransactionId();
validateAccount();
validateAmount(amount, "取款金额必须大于0");
if (amount > balance) {
throw new InsufficientFundsException(balance, amount);
}
balance -= amount;
recordTransaction(transactionId, InvalidTransactionException.TransactionType.WITHDRAWAL, amount);
System.out.println(String.format("取款成功:%.2f,当前余额:%.2f", amount, balance));
}
// 转账
public void transfer(BankAccount targetAccount, double amount) throws BankException {
String transactionId = generateTransactionId();
try {
validateAccount();
targetAccount.validateAccount();
validateAmount(amount, "转账金额必须大于0");
if (amount > balance) {
throw new InsufficientFundsException(balance, amount);
}
// 执行转账
this.balance -= amount;
targetAccount.balance += amount;
// 记录交易
recordTransaction(transactionId, InvalidTransactionException.TransactionType.TRANSFER, amount);
targetAccount.recordTransaction(transactionId, InvalidTransactionException.TransactionType.TRANSFER, amount);
System.out.println(String.format("转账成功:从 %s 转给 %s,金额:%.2f",
this.ownerName, targetAccount.ownerName, amount));
} catch (BankException e) {
throw e; // 重新抛出银行异常
} catch (Exception e) {
throw new InvalidTransactionException(
"转账失败:" + e.getMessage(),
InvalidTransactionException.TransactionType.TRANSFER,
transactionId
);
}
}
// 验证账户状态
private void validateAccount() {
if (isLocked) {
throw new AccountLockedException(accountNumber, "账户因多次操作失败被锁定");
}
}
// 验证金额
private void validateAmount(double amount, String errorMessage) throws InvalidTransactionException {
if (amount <= 0) {
failedAttempts++;
if (failedAttempts >= MAX_FAILED_ATTEMPTS) {
isLocked = true;
throw new AccountLockedException(accountNumber, "多次无效操作");
}
throw new InvalidTransactionException(
errorMessage,
InvalidTransactionException.TransactionType.DEPOSIT,
generateTransactionId()
);
}
// 重置失败次数
failedAttempts = 0;
}
// 记录交易
private void recordTransaction(String transactionId,
InvalidTransactionException.TransactionType type,
double amount) {
Transaction transaction = new Transaction(transactionId, type, amount, new Date());
transactionHistory.put(transactionId, transaction);
}
// 生成交易ID
private String generateTransactionId() {
return "TXN-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
}
// 解锁账户(管理员操作)
public void unlockAccount() {
this.isLocked = false;
this.failedAttempts = 0;
System.out.println("账户 " + accountNumber + " 已解锁");
}
// 获取账户信息
public void printAccountInfo() {
System.out.println("=== 账户信息 ===");
System.out.println("账号:" + accountNumber);
System.out.println("户主:" + ownerName);
System.out.println("余额:" + String.format("%.2f", balance));
System.out.println("状态:" + (isLocked ? "锁定" : "正常"));
System.out.println("失败次数:" + failedAttempts);
System.out.println("交易记录数:" + transactionHistory.size());
}
// Getter方法
public String getAccountNumber() {
return accountNumber;
}
public String getOwnerName() {
return ownerName;
}
public double getBalance() {
return balance;
}
public boolean isLocked() {
return isLocked;
}
// 内部类:交易记录
private static class Transaction {
private String id;
private InvalidTransactionException.TransactionType type;
private double amount;
private Date timestamp;
public Transaction(String id, InvalidTransactionException.TransactionType type,
double amount, Date timestamp) {
this.id = id;
this.type = type;
this.amount = amount;
this.timestamp = timestamp;
}
@Override
public String toString() {
return String.format("%s: %s %.2f at %s",
id, type.getDescription(), amount, timestamp);
}
}
}
6.3.3 自定义异常的综合应用
/**
* 自定义异常综合演示
*/
public class CustomExceptionDemo {
public static void main(String[] args) {
System.out.println("=== 自定义异常综合演示 ===");
// 创建银行账户
BankAccount account1 = new BankAccount("ACC001", "张三", 1000.0);
BankAccount account2 = new BankAccount("ACC002", "李四", 500.0);
// 演示各种异常情况
demonstrateValidOperations(account1, account2);
demonstrateInsufficientFunds(account1);
demonstrateInvalidTransactions(account1);
demonstrateAccountLocking(account1);
demonstrateExceptionChaining();
}
public static void demonstrateValidOperations(BankAccount account1, BankAccount account2) {
System.out.println("\n--- 正常操作演示 ---");
try {
account1.printAccountInfo();
account1.deposit(200.0);
account1.withdraw(150.0);
account1.transfer(account2, 100.0);
System.out.println("\n操作完成后的账户状态:");
account1.printAccountInfo();
account2.printAccountInfo();
} catch (BankException e) {
System.out.println("银行业务异常: " + e);
}
}
public static void demonstrateInsufficientFunds(BankAccount account) {
System.out.println("\n--- 余额不足异常演示 ---");
try {
account.withdraw(2000.0); // 余额不足
} catch (InsufficientFundsException e) {
System.out.println("捕获余额不足异常:");
System.out.println(" 异常信息: " + e.getMessage());
System.out.println(" 错误代码: " + e.getErrorCode());
System.out.println(" 当前余额: " + e.getCurrentBalance());
System.out.println(" 请求金额: " + e.getRequestedAmount());
System.out.println(" 缺少金额: " + e.getShortfall());
} catch (BankException e) {
System.out.println("其他银行异常: " + e);
}
}
public static void demonstrateInvalidTransactions(BankAccount account) {
System.out.println("\n--- 无效交易异常演示 ---");
// 测试无效金额
try {
account.deposit(-100.0); // 负数金额
} catch (InvalidTransactionException e) {
System.out.println("捕获无效交易异常:");
System.out.println(" " + e);
System.out.println(" 交易类型: " + e.getTransactionType().getDescription());
System.out.println(" 交易ID: " + e.getTransactionId());
}
try {
account.withdraw(0); // 零金额
} catch (InvalidTransactionException e) {
System.out.println("\n再次捕获无效交易异常:");
System.out.println(" " + e);
} catch (BankException e) {
System.out.println("其他银行异常: " + e);
}
}
public static void demonstrateAccountLocking(BankAccount account) {
System.out.println("\n--- 账户锁定异常演示 ---");
// 连续进行无效操作,触发账户锁定
for (int i = 1; i <= 4; i++) {
try {
System.out.println("\n第 " + i + " 次无效操作:");
account.deposit(-50.0);
} catch (AccountLockedException e) {
System.out.println("账户被锁定:");
System.out.println(" 账户号: " + e.getAccountNumber());
System.out.println(" 锁定原因: " + e.getLockReason());
System.out.println(" 锁定时间: " + e.getLockTime());
break;
} catch (InvalidTransactionException e) {
System.out.println("无效交易: " + e.getMessage());
}
}
// 尝试在锁定状态下操作
try {
System.out.println("\n尝试在锁定状态下存款:");
account.deposit(100.0);
} catch (AccountLockedException e) {
System.out.println("操作失败,账户已锁定: " + e.getMessage());
} catch (Exception e) {
System.out.println("其他异常: " + e.getMessage());
}
// 解锁账户
account.unlockAccount();
try {
System.out.println("\n解锁后尝试存款:");
account.deposit(100.0);
} catch (Exception e) {
System.out.println("操作失败: " + e.getMessage());
}
}
public static void demonstrateExceptionChaining() {
System.out.println("\n--- 异常链演示 ---");
try {
simulateComplexOperation();
} catch (BankException e) {
System.out.println("捕获顶层异常: " + e.getMessage());
// 检查异常链
Throwable cause = e.getCause();
int level = 1;
while (cause != null) {
System.out.println(" 原因 " + level + ": " + cause.getClass().getSimpleName() +
" - " + cause.getMessage());
cause = cause.getCause();
level++;
}
System.out.println("\n完整堆栈跟踪:");
e.printStackTrace();
}
}
private static void simulateComplexOperation() throws BankException {
try {
// 模拟底层操作失败
throw new RuntimeException("数据库连接失败");
} catch (RuntimeException e) {
// 包装为业务异常
throw new BankException("银行系统暂时不可用,请稍后重试", "SYSTEM_ERROR", e);
}
}
}
6.4 异常处理最佳实践
6.4.1 异常处理原则
import java.io.*;
import java.util.logging.Logger;
import java.util.logging.Level;
/**
* 异常处理最佳实践演示
*/
public class ExceptionBestPractices {
private static final Logger logger = Logger.getLogger(ExceptionBestPractices.class.getName());
public static void main(String[] args) {
System.out.println("=== 异常处理最佳实践 ===");
demonstrateBestPractices();
}
public static void demonstrateBestPractices() {
// 1. 具体异常处理
demonstrateSpecificExceptionHandling();
// 2. 资源管理
demonstrateResourceManagement();
// 3. 异常日志记录
demonstrateExceptionLogging();
// 4. 异常转换
demonstrateExceptionTranslation();
// 5. 失败快速原则
demonstrateFailFast();
}
// 最佳实践1: 捕获具体的异常类型
public static void demonstrateSpecificExceptionHandling() {
System.out.println("\n--- 具体异常处理 ---");
// ❌ 不好的做法:捕获过于宽泛的异常
try {
riskyOperation();
} catch (Exception e) {
// 这样会捕获所有异常,包括不应该处理的
System.out.println("捕获到异常(不推荐): " + e.getMessage());
}
// ✅ 好的做法:捕获具体的异常类型
try {
riskyOperation();
} catch (FileNotFoundException e) {
System.out.println("文件未找到,使用默认配置");
// 具体的恢复逻辑
} catch (IOException e) {
System.out.println("IO错误,重试操作: " + e.getMessage());
// 重试逻辑
} catch (SecurityException e) {
System.out.println("权限不足: " + e.getMessage());
// 权限处理逻辑
}
}
private static void riskyOperation() throws IOException {
// 模拟可能抛出不同异常的操作
throw new FileNotFoundException("配置文件未找到");
}
// 最佳实践2: 正确的资源管理
public static void demonstrateResourceManagement() {
System.out.println("\n--- 资源管理 ---");
// ❌ 不好的做法:可能导致资源泄漏
badResourceManagement();
// ✅ 好的做法:使用try-with-resources
goodResourceManagement();
}
private static void badResourceManagement() {
FileInputStream fis = null;
try {
fis = new FileInputStream("test.txt");
// 处理文件
} catch (IOException e) {
System.out.println("文件操作失败: " + e.getMessage());
// 如果这里return或抛出异常,finally可能不会执行
return;
} finally {
// 这种方式容易出错
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
System.out.println("关闭文件失败: " + e.getMessage());
}
}
}
}
private static void goodResourceManagement() {
// 使用try-with-resources自动管理资源
try (FileInputStream fis = new FileInputStream("test.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(fis))) {
// 处理文件
String line = reader.readLine();
System.out.println("读取内容: " + line);
} catch (IOException e) {
System.out.println("文件操作失败: " + e.getMessage());
}
// 资源会自动关闭
}
// 最佳实践3: 异常日志记录
public static void demonstrateExceptionLogging() {
System.out.println("\n--- 异常日志记录 ---");
try {
operationThatMightFail();
} catch (Exception e) {
// ✅ 记录详细的异常信息
logger.log(Level.SEVERE, "操作失败", e);
// ✅ 提供用户友好的错误信息
System.out.println("操作暂时无法完成,请稍后重试");
// ❌ 不要只打印堆栈跟踪而不处理
// e.printStackTrace(); // 仅用于调试
}
}
private static void operationThatMightFail() throws Exception {
throw new Exception("模拟操作失败");
}
// 最佳实践4: 异常转换
public static void demonstrateExceptionTranslation() {
System.out.println("\n--- 异常转换 ---");
try {
businessOperation();
} catch (BusinessException e) {
System.out.println("业务异常: " + e.getMessage());
System.out.println("错误代码: " + e.getErrorCode());
}
}
// 将底层异常转换为业务异常
private static void businessOperation() throws BusinessException {
try {
// 模拟底层操作
throw new SQLException("数据库连接失败");
} catch (SQLException e) {
// 转换为业务异常,隐藏底层实现细节
throw new BusinessException("用户数据获取失败", "USER_DATA_ERROR", e);
}
}
// 最佳实践5: 失败快速原则
public static void demonstrateFailFast() {
System.out.println("\n--- 失败快速原则 ---");
try {
processUserData(null, -1);
} catch (IllegalArgumentException e) {
System.out.println("参数验证失败: " + e.getMessage());
}
}
// ✅ 在方法开始时验证参数,快速失败
private static void processUserData(String userData, int userId) {
// 参数验证 - 失败快速
if (userData == null) {
throw new IllegalArgumentException("用户数据不能为null");
}
if (userData.trim().isEmpty()) {
throw new IllegalArgumentException("用户数据不能为空");
}
if (userId <= 0) {
throw new IllegalArgumentException("用户ID必须大于0");
}
// 实际处理逻辑
System.out.println("处理用户数据: " + userData + ", ID: " + userId);
}
// 自定义业务异常
private static class BusinessException extends Exception {
private String errorCode;
public BusinessException(String message, String errorCode, Throwable cause) {
super(message, cause);
this.errorCode = errorCode;
}
public String getErrorCode() {
return errorCode;
}
}
// 模拟SQL异常
private static class SQLException extends Exception {
public SQLException(String message) {
super(message);
}
}
}
6.4.2 异常处理反模式
/**
* 异常处理反模式演示 - 应该避免的做法
*/
public class ExceptionAntiPatterns {
public static void main(String[] args) {
System.out.println("=== 异常处理反模式(应避免的做法)===");
demonstrateAntiPatterns();
}
public static void demonstrateAntiPatterns() {
// 反模式1: 吞噬异常
demonstrateSwallowingExceptions();
// 反模式2: 过度使用异常
demonstrateOverusingExceptions();
// 反模式3: 异常作为控制流
demonstrateExceptionsAsControlFlow();
// 反模式4: 不恰当的异常信息
demonstrateInappropriateExceptionMessages();
}
// 反模式1: 吞噬异常(静默忽略)
public static void demonstrateSwallowingExceptions() {
System.out.println("\n--- 反模式1: 吞噬异常 ---");
// ❌ 非常糟糕的做法:完全忽略异常
try {
int result = 10 / 0;
} catch (Exception e) {
// 什么都不做 - 这是最糟糕的做法!
}
// ❌ 稍好但仍然不好:只打印但不处理
try {
int result = 10 / 0;
} catch (Exception e) {
e.printStackTrace(); // 只打印,没有实际处理
}
// ✅ 正确的做法:记录并适当处理
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("算术运算错误,使用默认值: " + e.getMessage());
// 提供默认值或其他恢复逻辑
int result = 0;
}
}
// 反模式2: 过度使用异常
public static void demonstrateOverusingExceptions() {
System.out.println("\n--- 反模式2: 过度使用异常 ---");
// ❌ 不好的做法:为正常的业务逻辑使用异常
try {
String result = findUserBad("nonexistent");
System.out.println("找到用户: " + result);
} catch (UserNotFoundException e) {
System.out.println("用户不存在(通过异常处理)");
}
// ✅ 好的做法:使用返回值处理正常的业务情况
String result = findUserGood("nonexistent");
if (result != null) {
System.out.println("找到用户: " + result);
} else {
System.out.println("用户不存在(通过返回值判断)");
}
}
// 不好的做法:用异常处理正常业务逻辑
private static String findUserBad(String username) throws UserNotFoundException {
if (!"admin".equals(username)) {
throw new UserNotFoundException("用户不存在: " + username);
}
return "Admin User";
}
// 好的做法:用返回值处理正常情况
private static String findUserGood(String username) {
if ("admin".equals(username)) {
return "Admin User";
}
return null; // 或者使用Optional<String>
}
// 反模式3: 异常作为控制流
public static void demonstrateExceptionsAsControlFlow() {
System.out.println("\n--- 反模式3: 异常作为控制流 ---");
// ❌ 不好的做法:使用异常控制程序流程
try {
for (int i = 0; ; i++) {
System.out.println("处理元素: " + getElementBad(i));
}
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组遍历完成(通过异常)");
}
// ✅ 好的做法:使用正常的控制结构
String[] array = {"A", "B", "C"};
for (int i = 0; i < array.length; i++) {
System.out.println("处理元素: " + array[i]);
}
System.out.println("数组遍历完成(正常流程)");
}
private static String getElementBad(int index) {
String[] array = {"A", "B", "C"};
return array[index]; // 故意让它越界来控制循环
}
// 反模式4: 不恰当的异常信息
public static void demonstrateInappropriateExceptionMessages() {
System.out.println("\n--- 反模式4: 不恰当的异常信息 ---");
try {
validateUserInputBad("");
} catch (Exception e) {
System.out.println("捕获到异常: " + e.getMessage());
}
try {
validateUserInputGood("");
} catch (Exception e) {
System.out.println("捕获到异常: " + e.getMessage());
}
}
// ❌ 不好的做法:异常信息不明确
private static void validateUserInputBad(String input) {
if (input == null || input.trim().isEmpty()) {
throw new RuntimeException("错误"); // 信息太模糊
}
}
// ✅ 好的做法:提供清晰、有用的异常信息
private static void validateUserInputGood(String input) {
if (input == null) {
throw new IllegalArgumentException("用户输入不能为null");
}
if (input.trim().isEmpty()) {
throw new IllegalArgumentException("用户输入不能为空字符串");
}
}
// 自定义异常
private static class UserNotFoundException extends Exception {
public UserNotFoundException(String message) {
super(message);
}
}
}
本章小结
本章详细介绍了Java异常处理机制:
异常基础:
- 异常是程序运行时发生的错误事件
- Java异常层次结构:Throwable → Error/Exception → RuntimeException
- 检查异常vs非检查异常的区别
异常处理语法:
try-catch
:捕获和处理异常finally
:无论是否发生异常都会执行的代码块try-with-resources
:自动资源管理throws
:声明方法可能抛出的异常throw
:主动抛出异常
自定义异常:
- 继承Exception(检查异常)或RuntimeException(非检查异常)
- 提供有意义的异常信息和错误代码
- 异常链:保留原始异常信息
最佳实践:
- 捕获具体的异常类型
- 正确管理资源
- 记录异常日志
- 异常转换和封装
- 失败快速原则
- 避免异常反模式
下一章预告
下一章我们将学习Java集合框架,包括List、Set、Map等常用集合类的使用方法和特点。
练习题
- 创建一个文件处理系统,包含自定义异常和完整的异常处理机制
- 设计一个用户注册系统,使用异常处理验证用户输入
- 实现一个简单的计算器,处理各种可能的异常情况
- 创建一个网络请求模拟器,演示异常链和异常转换的使用