Java 异常体系与处理规范手册
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 两类异常对比表
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 的优势与原理
核心优势:
自动关闭资源:资源对象在try块结束后自动关闭,无需手动写finally。
代码简化:减少嵌套try-catch,代码更简洁。
避免资源泄漏: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"); // 调用方法,无需处理异常(已在方法内捕获)
}
}对比分析
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;
}
}测试结果(接口调用)
非法请求(订单 ID=0):
请求 URL:http://localhost:8080/order/0
返回 JSON(全局捕获 OrderException):
{
"code": 400,
"message": "订单ID非法:0(必须大于0)",
"success": false
}正常请求(订单 ID=1):
返回 JSON(正常业务数据):
{
"orderId": 1,
"productName": "Java编程书籍",
"price": 59.9,
"success": true
}4. 异常处理核心注意事项
避免吞异常:不要在catch块中只打印日志却不抛出异常,也不要空catch块(会导致问题无法定位)。
异常信息明确:异常信息需包含关键上下文(如 “订单 ID=0 非法” 而非 “参数非法”),便于排查问题。
优先处理具体异常:catch块应先捕获具体异常(如OrderException),再捕获通用异常(如Exception),避免具体异常被通用异常覆盖。
资源关闭用 try-with-resources:处理 IO 流、数据库连接等资源时,优先使用try-with-resources,避免手动关闭导致的资源泄漏。
自定义异常用 Unchecked 异常:业务异常建议继承RuntimeException(非受检),避免强制处理导致代码冗余(如OrderException)。