Error、RuntimeException、IOException 三者完整区别

一、顶层分类(Java 异常体系)

java 复制代码
Throwable
├─ Error 错误(系统级,不用捕获)
└─ Exception 异常
   ├─ RuntimeException 运行时异常(非受检异常)
   └─ 其他Exception(受检异常,必须try-catch/throws)
      └─ IOException 属于这类

1. Error

  • 归属:Throwable 直接子类,不是 Exception
  • 性质:系统严重错误,程序无法恢复
  • 典型例子:
    • OOM:OutOfMemoryError
    • 栈溢出:StackOverflowError
    • 类加载失败:NoClassDefFoundError
  • 特点:
    1. 编译器不强制捕获 / 抛出
    2. 发生后程序基本只能崩溃,无法处理
    3. Spring @Transactional 默认遇到 Error 会回滚事务

2. RuntimeException 运行时异常(非受检异常 Unchecked)

  • 归属:Exception 的子类
  • 性质:代码逻辑错误,运行时才暴露
  • 典型:
    • NullPointerException 空指针
    • ArrayIndexOutOfBoundsException 数组越界
    • IllegalArgumentException 参数非法
    • ArithmeticException 除零
  • 特点:
    1. 编译器不强制 try-catch /throws
    2. 属于代码 bug,规范编码可避免
    3. @Transactional 默认遇到它自动回滚

3. IOException IO 异常(受检异常 Checked Exception)

  • 归属:直接继承 Exception不是 RuntimeException
  • 性质:外部资源故障,和业务无关,无法完全避免
  • 典型:文件读写、网络连接、流关闭失败
  • 特点:
    1. 编译器强制处理:要么 try-catch,要么方法 throws IOException
    2. Spring 默认不会触发事务回滚 这是最关键业务区别!

二、核心对比表

表格

类型 受检 / 非受检 默认 @Transactional 是否回滚 产生原因 是否强制捕获
Error 非受检 ✅ 自动回滚 JVM / 系统底层故障 不强制
RuntimeException 非受检 ✅ 自动回滚 代码逻辑 bug 不强制
IOException 受检 Checked ❌ 默认不回滚 IO、网络、文件异常 必须捕获 / 抛出

三、@Transactional 事务回滚规则重点

Spring 默认回滚规则:

只有 RuntimeException、Error 才会自动回滚 所有普通 Checked 异常(IOException、SQLException 等)捕获 / 抛出都不回滚

场景示例

java 复制代码
// 默认配置,IO异常不会回滚
@Transactional
public void readFile() throws IOException {
    insertDb();
    readFile(); // 抛IOException,数据库插入不会回滚
}

解决:指定所有异常都回滚

java 复制代码
@Transactional(rollbackFor = Exception.class)
public void readFile() throws IOException {
    insertDb();
    readFile(); // IOException也会触发回滚
}

四、简单记忆口诀

  1. Error:系统崩了,管不了,事务回滚
  2. RuntimeException:代码写错,运行炸,事务回滚
  3. IOException:外部 IO 问题,编译器逼你处理,默认不回滚事务

五、Java 处理 IOException 的两种标准方式

IOException受检异常(Checked Exception),编译器强制要求必须处理,二选一:

方式 1:throws 向上抛出(推荐 Service / 工具层)

方法声明抛出异常,交给上层调用方处理,代码干净,适合事务场景。

java 复制代码
import java.io.FileReader;
import java.io.IOException;

// 抛出IOException,由调用者捕获
public static String readFile() throws IOException {
    FileReader reader = new FileReader("test.txt");
    char c = (char) reader.read();
    reader.close();
    return String.valueOf(c);
}

配合 @Transactional 注意

只抛 IOException 默认不会回滚事务,需要配置:

java 复制代码
@Transactional(rollbackFor = Exception.class)
public void biz() throws IOException {
    readFile();
}

方式 2:try-catch 本地捕获(当前方法内处理)

适合在当前方法就能兜底、不需要上层感知的场景。

java 复制代码
public static void readFileCatch() {
    FileReader reader = null;
    try {
        reader = new FileReader("test.txt");
        System.out.println((char) reader.read());
    } catch (IOException e) {
        // 异常处理:打印日志、返回错误码、抛出自定义业务异常
        e.printStackTrace();
        log.error("读取文件失败", e);
        throw new BusinessException("文件读取异常");
    } finally {
        // 必须关闭流,释放资源
        try {
            if (reader != null) {
                reader.close();
            }
        } catch (IOException e) {
            log.error("关闭流失败", e);
        }
    }
}

六、优雅写法:try-with-resources(JDK7+,最优)

实现 AutoCloseable 的流(FileReader、InputStream、Socket 等)自动关闭,不用手动写 finally 关闭,大幅简化 IO 代码。

java 复制代码
// 资源写在try()中,代码块结束自动关闭,无论正常/异常
public static void readFileAutoClose() {
    try (FileReader reader = new FileReader("test.txt")) {
        char ch = (char) reader.read();
        System.out.println(ch);
    } catch (IOException e) {
        log.error("文件读取异常", e);
        // 向上抛出自定义异常,触发事务回滚
        throw new RuntimeException("文件操作失败", e);
    }
}

七、业务开发最佳实践

  1. 工具类 / 底层 IO 方法 :使用 throws IOException 抛出,不吞异常;
  2. Service 业务层:捕获 IO 异常后包装为运行时异常抛出,配合事务回滚:
java 复制代码
@Transactional(rollbackFor = Exception.class)
public void importData() {
    try (BufferedReader br = Files.newBufferedReader(Paths.get("data.csv"))) {
        String line;
        while ((line = br.readLine()) != null) {
            saveDb(line);
        }
    } catch (IOException e) {
        log.error("导入文件失败", e);
        // 包装为RuntimeException,触发事务回滚
        throw new RuntimeException("文件读取失败", e);
    }
}
  1. 禁止空 catch :不要只写 catch(IOException e){ } 吃掉异常,会丢失报错线索;
  2. 网络、文件、FTP、OSS 操作全部属于 IO,统一用 try-with-resources

八、核心总结

  1. IOException 是受检异常,必须 throwstry-catch
  2. JDK7+ 优先 try-with-resources 自动关闭流;
  3. Spring 事务中,单纯抛出 IOException 不回滚,要么配置rollbackFor=Exception.class,要么 catch 后转抛 RuntimeException;
  4. 捕获后务必打印完整异常日志,便于排查。