1. 什么是异常?为什么要处理异常?

在程序运行过程中,总会遇到一些意料之外的情况:用户输入了非法数据、网络突然断开、文件找不到、数据库连接超时……这些在 Java 中统称为异常(Exception)

如果不处理异常,程序会直接崩溃退出,用户看到的将是满屏的红色错误堆栈——这对用户体验来说是灾难性的。因此,Java 提供了一套完善的异常处理机制,让我们能够:

  • 捕获异常:在异常发生时拦截它,不让程序崩溃
  • 处理异常:给出友好的提示、记录日志、尝试恢复
  • 清理资源:确保文件流、网络连接等资源被正确关闭

2. Java异常体系结构

Java 的异常类都继承自 Throwable,主要分为两大类:

Throwable
├── Error(错误)—— 程序无法处理
└── Exception(异常)
    ├── RuntimeException(运行时异常)—— 不强制处理,如 NullPointerException
    └── 非运行时异常(Checked Exception)—— 必须处理

新手最容易混淆的点RuntimeException 及其子类(如 NullPointerExceptionArrayIndexOutOfBoundsException)可以不写 try-catch,编译器不会报错;而 IOException 这类 Checked Exception,编译器强制你处理,否则无法通过编译。

3. 异常处理的核心语法

3.1 try-catch 基本结构

try {
    // 可能发生异常的代码
} catch (异常类型 变量名) {
    // 异常发生时的处理代码
}

3.2 多重 catch

一个 try 块后面可以跟多个 catch 块,分别捕获不同类型的异常。顺序为:子类异常在前,父类异常在后。

try {
    // 可能抛出多种异常的代码
} catch (FileNotFoundException e) {
    System.out.println("文件未找到");
} catch (IOException e) {
    System.out.println("IO异常");
} catch (Exception e) {
    System.out.println("其他异常");
}

3.3 finally 块——资源清理的保障

finally 块中的代码无论是否发生异常,都会执行。这保证了资源释放操作不会被跳过。

try {
    // 使用资源
} catch (Exception e) {
    // 处理异常
} finally {
    // 关闭资源(一定会执行)
}

3.4 完整执行流程

try → 无异常 → 跳过 catch → 执行 finally
try → 有异常 → 进入匹配的 catch → 执行 finally
try → 有异常但未捕获 → 执行 finally → 异常继续向上抛

4. 自定义异常

当 Java 内置的异常类型无法准确描述业务问题时,我们可以创建自己的异常类。

4.1 为什么要自定义异常?

语义更清晰;便于精准捕获:可以针对特定业务异常做不同处理;便于日志和监控:按异常类型统计错误分布。

4.2 如何自定义异常?

// 继承 Exception 就是 Checked Exception(必须处理)
class MyBusinessException extends Exception {
    public MyBusinessException(String message) {
        super(message);
    }
}

// 继承 RuntimeException 就是运行时异常(可选处理)
class MyRuntimeBusinessException extends RuntimeException {
    public MyRuntimeBusinessException(String message) {
        super(message);
    }
}

新手建议:在支付、文件操作等需要强制调用方处理的场景,继承 Exception;在参数校验等内部逻辑场景,继承 RuntimeException

5. throws 与 throw 关键字

5.1 throw——主动抛出异常

public void checkAge(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("年龄不能为负数");
    }
}

5.2 throws——声明可能抛出的异常

如果方法内部抛出了 Checked Exception 但不想在当前方法中处理,可以用 throws 声明,让调用者去处理。

public void readFile(String path) throws IOException {
    FileReader reader = new FileReader(path);
    // ...
}

6. 实战案例:支付网关异常处理

现在我们把前面学到的知识综合起来,模拟一个电商支付场景。

6.1 需求分析

支付过程中可能遇到三种典型故障:

  1. 网络超时——临时性问题,可以重试
  2. 支付被拒绝——余额不足、风控拦截,需要提示用户
  3. 系统内部错误——需要记录日志并告警

6.2 第一步:自定义异常类

/**
 * 网络超时异常
 */
class NetworkTimeoutException extends Exception {
    public NetworkTimeoutException() {
        super("Network Timeout");
    }
}

/**
 * 支付被拒绝异常(如余额不足)
 */
class PaymentDeclinedException extends Exception {
    public PaymentDeclinedException() {
        super("Payment Declined");
    }
}

/**
 * 支付系统内部错误异常
 */
class SystemErrorException extends Exception {
    public SystemErrorException() {
        super("System Error");
    }
}

6.3 第二步:编写支付网关类

/**
 * 支付网关模拟类
 */
class PaymentGateway {
    
    public void connect() {
        System.out.println("Gateway Connected");
    }
    
    public void disconnect() {
        System.out.println("Gateway Disconnected");
    }
    
    /**
     * 处理支付
     * @param amount 支付金额
     * @param errorCode 错误码(0-成功,1-网络超时,2-支付拒绝,3-系统错误)
     */
    public void processPayment(double amount, int errorCode) 
            throws NetworkTimeoutException, PaymentDeclinedException, SystemErrorException {
        
        switch (errorCode) {
            case 0:
                System.out.printf("Payment Success: %.2f%n", amount);
                break;
            case 1:
                throw new NetworkTimeoutException();
            case 2:
                throw new PaymentDeclinedException();
            case 3:
                throw new SystemErrorException();
            default:
                throw new IllegalArgumentException("Invalid error code: " + errorCode);
        }
    }
}

6.4 第三步:主程序

public class Main {
    public static void main(String[] args) {
        PaymentGateway gateway = new PaymentGateway();
        
        // 1. 建立连接
        gateway.connect();
        
        try {
            // 2. 尝试支付(模拟网络超时)
            gateway.processPayment(199.99, 1);
            
        } catch (NetworkTimeoutException e) {
            System.out.println("捕获异常: " + e.getMessage());
            System.out.println("→ 建议:稍后重试");
            
        } catch (PaymentDeclinedException e) {
            System.out.println("捕获异常: " + e.getMessage());
            System.out.println("→ 建议:检查余额或支付方式");
            
        } catch (SystemErrorException e) {
            System.out.println("捕获异常: " + e.getMessage());
            System.out.println("→ 建议:联系客服");
            
        } finally {
            // 3. 无论成功还是失败,都必须断开连接
            gateway.disconnect();
            System.out.println("资源已清理,连接已关闭");
        }
    }
}

6.5 运行结果

Gateway Connected
捕获异常: Network Timeout
→ 建议:稍后重试
Gateway Disconnected
资源已清理,连接已关闭

异常处理不是可有可无的"装饰",而是生产级代码的基本素养。希望你能在每一个项目中,都认真对待异常处理,写出更健壮、更可靠的 Java 程序。

更多推荐