1. 异常体系核心区别

Java 异常体系基于Throwable类,分为Error(错误,如 OutOfMemoryError,无需捕获)和Exception(异常,需处理),其中Exception又分为Checked 异常Unchecked 异常,两者在编译要求、处理方式上差异显著。

1.1 Checked 异常(受检异常):以 IOException 为例

  • 定义:编译期强制要求处理的异常,继承自Exception但不继承RuntimeException,通常是程序外部环境导致的异常(如文件不存在、网络中断)。

  • 典型案例:IOException(IO 操作异常)、SQLException(数据库操作异常)。

  • 核心特征

    • 编译报错:若未通过try-catch捕获或throws声明抛出,编译器直接报错,无法通过编译。

  • 处理方式:要么用try-catch主动捕获并处理,要么在方法签名加throws声明,将异常抛给调用者处理。

  • 代码示例(IOException 处理)

import java.io.FileReader;
import java.io.IOException;

public class CheckedExceptionDemo {

    // 方法1:用throws声明抛出异常,交给调用者处理

    public static void readFile1(String path) throws IOException {
        FileReader reader = new FileReader(path); // 编译期提示:需处理IOException
        reader.read();
        reader.close();
    }

    // 方法2:用try-catch主动捕获异常
    public static void readFile2(String path) {

        FileReader reader = null;
        try {
            reader = new FileReader(path);
            reader.read();
        } catch (IOException e) {
            // 异常处理逻辑(如打印日志、提示用户)
            System.out.println("文件读取失败:" + e.getMessage());
        } finally {

            // 关闭资源(避免资源泄漏)
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    System.out.println("资源关闭失败:" + e.getMessage());
                }
            }
        }
    }
}

1.2 Unchecked 异常(非受检异常):以 NullPointerException 为例

  • 定义:编译期不强制处理的异常,继承自RuntimeException,通常是程序逻辑错误导致的异常(如空指针、数组越界)。

  • 典型案例:NullPointerException(空指针异常)、IndexOutOfBoundsException(数组越界异常)、IllegalArgumentException(非法参数异常)。

  • 核心特征

    • 编译不报错:即使不处理,编译器也能通过,但运行时可能抛出异常导致程序崩溃。

  • 处理建议:无需强制捕获,建议通过代码逻辑避免(如判空、校验参数),若需捕获,也可通过try-catch处理。

  • 代码示例(NullPointerException 避免与处理)

public class UncheckedExceptionDemo {
    public static void getLength(String str) {

        // 逻辑优化:避免空指针(推荐)
        if (str == null) {
            System.out.println("字符串不能为null");
            return;
        }

        // 若未判空,运行时可能抛出NullPointerException
        System.out.println("字符串长度:" + str.length());
    }

    public static void main(String[] args) {

        // 处理Unchecked异常(可选)
        try {
            getLength(null);
        } catch (NullPointerException e) {
            System.out.println("捕获空指针异常:" + e.getMessage());
        }
    }

}

1.3 两类异常对比表

对比维度

Checked 异常(受检)

Unchecked 异常(非受检)

父类

Exception(非 RuntimeException 子类)

RuntimeException

编译要求

强制处理(try-catch/throws)

不强制处理

常见案例

IOException、SQLException

NullPointerException、IndexOutOfBoundsException

产生原因

外部环境问题(如文件不存在)

程序逻辑错误(如未判空、参数非法)

处理建议

必须捕获或声明抛出

通过逻辑优化避免,必要时捕获

2. 异常处理规范:try-with-resources 详解

在处理 IO 流、数据库连接等需要手动关闭的资源时,传统finally写法冗余且易出错,Java 7引入的try-with-resources语法可自动关闭资源,简化代码并避免资源泄漏。

2.1 传统 finally 关闭资源的问题

  • 冗余代码:需在finally中手动关闭资源,且关闭资源本身可能抛出异常,需嵌套try-catch。

  • 资源泄漏风险:若try块中抛出异常,可能导致finally中资源关闭代码未执行(如早期 JVMbug 或极端场景)。

  • 示例(传统文件读取写法)

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class TraditionalResourceClose {

    public static void readFile(String path) {

        BufferedReader br = null; // 需定义在try外,否则finally无法访问
        try {
            br = new BufferedReader(new FileReader(path));
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.out.println("读取异常:" + e.getMessage());
        } finally {
            // 关闭资源,需嵌套try-catch(因为close()也可能抛IOException)
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    System.out.println("关闭流异常:" + e.getMessage());
                }
            }
        }
    }
}

2.2 try-with-resources 的优势与原理

  • 核心优势

  1. 自动关闭资源:资源对象在try块结束后自动关闭,无需手动写finally。

  1. 代码简化:减少嵌套try-catch,代码更简洁。

  1. 避免资源泄漏:JVM 保证资源一定会关闭,即使try块中抛出异常。

  • 使用条件:资源对象必须实现AutoCloseable接口(Java 7 + 中,IO 流、数据库连接等均已实现该接口)。

  • 语法格式

try (资源对象1 变量名1 = 资源初始化1; 资源对象2 变量名2 = 资源初始化2) {
    // 业务逻辑(使用资源)
} catch (异常类型 e) {
    // 异常处理
}

3. 实操任务实现

3.1 Demo1:try-with-resources 优化文件读取(对比传统写法)

优化后代码(try-with-resources)

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class TryWithResourcesDemo {

    public static void readFile(String path) {

        // 资源对象定义在try()中,自动关闭(无需finally)
        try (BufferedReader br = new BufferedReader(new FileReader(path))) {

            String line;

            // 读取文件内容
            while ((line = br.readLine()) != null) {
                System.out.println("文件内容:" + line);
            }

        } catch (IOException e) {
            // 统一捕获所有IO异常(包括read()和自动close()抛出的异常)
            System.out.println("文件操作异常:" + e.getMessage());
        }

    }

    public static void main(String[] args) {
        readFile("test.txt"); // 调用方法,无需处理异常(已在方法内捕获)
    }
}

对比分析

写法类型

代码行数

资源关闭方式

异常处理复杂度

资源泄漏风险

传统 finally 写法

~30 行

手动关闭(嵌套 try-catch)

高(需处理业务异常 + 关闭异常)

中(可能漏关)

try-with-resources 写法

~15 行

自动关闭(JVM 保证)

低(统一捕获)

低(无漏关)

3.2 Demo2:自定义业务异常 +@ControllerAdvice 全局捕获

在 Spring Boot 项目中,自定义业务异常(如订单异常),并通过@ControllerAdvice+@ExceptionHandler实现全局异常捕获,统一返回格式。

步骤 1:自定义业务异常(OrderException)

// 自定义业务异常,继承RuntimeException(Unchecked异常,无需强制处理)
public class OrderException extends RuntimeException {

    // 异常状态码(如400:参数错误,500:服务器错误)
    private int code;

    // 构造方法
    public OrderException(int code, String message) {
        super(message); // 调用父类构造,设置异常信息
        this.code = code;
    }

    // Getter方法(获取状态码)
    public int getCode() {
        return code;
    }
}

步骤 2:全局异常捕获类(GlobalExceptionHandler)

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;

// 全局异常捕获:处理所有Controller层抛出的异常

@ControllerAdvice

public class GlobalExceptionHandler {

    // 捕获OrderException(自定义业务异常)
    @ExceptionHandler(OrderException.class)
    @ResponseBody // 返回JSON格式数据
    public Map<String, Object> handleOrderException(OrderException e) {

        Map<String, Object> result = new HashMap<>();
        result.put("code", e.getCode()); // 自定义状态码
        result.put("message", e.getMessage()); // 异常信息
        result.put("success", false); // 业务状态(失败)
        return result;
    }

    // 捕获其他所有异常(兜底处理)

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Map<String, Object> handleException(Exception e) {

        Map<String, Object> result = new HashMap<>();
        result.put("code", 500); // 通用错误码(服务器内部错误)
        result.put("message", "系统异常:" + e.getMessage());
        result.put("success", false);
        return result;
    }

}

步骤 3:Controller 层测试(抛出自定义异常)

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RestController;

@RestController

public class OrderController {

    // 模拟订单查询接口:若订单ID<1,抛出OrderException

    @GetMapping("/order/{id}")
    public Map<String, Object> getOrder(@PathVariable Integer id) {

        // 业务校验:若订单ID非法,抛出自定义异常
        if (id < 1) {
            throw new OrderException(400, "订单ID非法:" + id + "(必须大于0)");
        }

        // 正常业务逻辑(模拟返回订单信息)
        Map<String, Object> order = new HashMap<>();
        order.put("orderId", id);
        order.put("productName", "Java编程书籍");
        order.put("price", 59.9);
        order.put("success", true);

        return order;
    }
}

测试结果(接口调用)

  1. 非法请求(订单 ID=0):

请求 URL:http://localhost:8080/order/0

返回 JSON(全局捕获 OrderException):

{
    "code": 400,
    "message": "订单ID非法:0(必须大于0)",
    "success": false
}

  1. 正常请求(订单 ID=1):

返回 JSON(正常业务数据):

{
    "orderId": 1,
    "productName": "Java编程书籍",
    "price": 59.9,
    "success": true
}

4. 异常处理核心注意事项

  1. 避免吞异常:不要在catch块中只打印日志却不抛出异常,也不要空catch块(会导致问题无法定位)。

  1. 异常信息明确:异常信息需包含关键上下文(如 “订单 ID=0 非法” 而非 “参数非法”),便于排查问题。

  1. 优先处理具体异常:catch块应先捕获具体异常(如OrderException),再捕获通用异常(如Exception),避免具体异常被通用异常覆盖。

  1. 资源关闭用 try-with-resources:处理 IO 流、数据库连接等资源时,优先使用try-with-resources,避免手动关闭导致的资源泄漏。

  1. 自定义异常用 Unchecked 异常:业务异常建议继承RuntimeException(非受检),避免强制处理导致代码冗余(如OrderException)。