Skip to content

异常处理

异常是程序在执行过程中发生的不正常事件,会中断正常的指令流程。Java 提供了完善的异常处理机制,帮助程序优雅地处理错误。

异常概述

异常体系

text
┌─────────────────────────────────────────────────────────────────┐
│                       Java 异常体系                              │
├─────────────────────────────────────────────────────────────────┤
│                        Throwable                                │
│                       /         \                               │
│                  Error          Exception                       │
│                 /    \         /        \                       │
│            VirtualMachineError  IOException  RuntimeException   │
│            OutOfMemoryError     SQLException  NullPointerException│
│            StackOverflowError                 ArrayIndexOutOfBounds│
│                                                                 │
│  Error:严重错误,程序无法处理                                    │
│  Exception:异常,程序可以处理                                    │
│    - 检查异常(Checked):编译时检查,必须处理                     │
│    - 运行时异常(Unchecked):运行时检查,可选处理                 │
└─────────────────────────────────────────────────────────────────┘

异常分类

类型说明示例
ErrorJVM 严重错误,程序无法恢复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 中常用的数据结构。