异常,不只是 try-catch

        在 Java 世界里,异常(Exception)是程序运行时处理“非预期状态”的核心机制。如何定义错误边界?如何传递失败信息?如何保证系统在异常路径下依然健壮?

一、Java 异常体系全景图

Java 中所有“可抛出”的类型都继承自 java.lang.Throwable,其派生出两个核心子类:

Throwable
├── Error             // JVM 或系统级严重错误,程序通常无法恢复
└── Exception         // 应用级异常,程序可捕获并处理
    ├── RuntimeException  // 运行时异常,编译器不强制处理
    └── 其他 Exception     // 受检异常(Checked Exception),必须显式处理
  • Error:留给 JVM,表示“系统崩了”,如 OutOfMemoryErrorStackOverflowError
  • Exception:留给开发者,表示“逻辑出错了”,如 IOExceptionSQLException
  • RuntimeException:表示“编程错误”,如 NullPointerExceptionArrayIndexOutOfBoundsException

二、Error:你不该捕获的“系统级灾难”

常见 Error 类型:

  1. OutOfMemoryError:堆/元空间/直接内存溢出
  2. StackOverflowError:递归过深或栈帧过大
  3. NoClassDefFoundError:类加载失败
  4. LinkageError:类版本冲突、方法签名不一致

为什么不建议捕获 Error

        例如捕获 OOM 后继续运行,可能导致数据错乱、连接泄漏、状态不一致 —— 比直接崩溃更危险!

三、Exception:受检 vs 非受检,设计的权衡

1. 受检异常(Checked Exception)

特点:编译器强制要求处理(try-catch 或 throws)

代表IOExceptionSQLExceptionClassNotFoundException

public void readFile(String path) throws IOException {
    FileReader reader = new FileReader(path); // 编译器强制你处理 IOException
    // ...
}

2. 非受检异常(Unchecked Exception / RuntimeException)

特点:编译器不强制处理,通常表示“编程错误”

代表:NullPointerExceptionIllegalArgumentExceptionIndexOutOfBoundsException

public void process(String input) {
    if (input == null) {
        throw new IllegalArgumentException(" "); // 主动抛出,语义清晰
    }
    // ...
}

四、try-catch-finally 执行机制深度解析

1. 基本执行顺序

public static int test() {
    int x = 1;
    try {
        System.out.println("try");
        return ++x;           // 计算返回值(x=2),暂存
    } catch (Exception e) {
        System.out.println("catch");
    } finally {
        System.out.println("finally");
        x = 3;                // 修改 x,但不影响已暂存的返回值
    }
    return x;                 // 这行不会执行
}
// 输出:
// try
// finally
// 返回值:2

核心机制

  1. return 表达式先计算,结果暂存
  2. finally 块无论如何都会执行(除 System.exit() 或 JVM 崩溃)
  3. finally 中的 return 会覆盖 try/catch 中的返回值

2. finally 中 return 的陷阱

public static int badExample() {
    try {
        return 1;
    } finally {
        return 2; // 覆盖了 try 的返回值,破坏逻辑,难以调试
    }
}

finally 只用于资源释放、状态清理、日志记录

绝不在 finally 中 return、throw、break、continue

五、Spring 中的全局异常处理:@ControllerAdvice

在 Web 应用中,我们通常在 Controller 层统一处理异常,避免每个方法写 try-catch。

1. 全局异常处理器示例

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
        log.warn("业务异常: {}", e.getMessage(), e);
        return ResponseEntity.badRequest()
                .body(new ErrorResponse(e.getCode(), e.getMessage()));
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleUnexpectedException(Exception e) {
        log.error("系统异常", e);
        return ResponseEntity.status(500)
                .body(new ErrorResponse("SYSTEM_ERROR", "系统繁忙,请稍后再试"));
    }
}

2. 错误响应体设计

public record ErrorResponse(String code, String message) {}

优势

  • 统一错误格式,前端可标准化处理
  • 避免异常堆栈暴露给用户(安全)
  • 日志集中记录,便于监控与排查

更多推荐