java的异常-Exception、Error

Java 异常机制是一种"分离正常逻辑与错误处理"的编程方式,合理使用可以让程序更健壮、更易维护。核心机制try-catch-finallythrows

Java 异常的继承体系

Java 中所有异常和错误都继承自 Throwable 类。其主要分为ErrorException两大类:异常是"可恢复的问题",错误是"不可恢复的灾难"

php 复制代码
                Throwable
                   |
        -------------------------
        |                       |
     Error                  Exception
                              |
                   -------------------------
                   |                       |
           Checked Exception       Unchecked Exception
                                   (RuntimeException 及其子类)

1. Error(错误)

  • 表示 JVM 无法处理的严重问题,通常程序无法恢复。

  • 常见例子:

    • OutOfMemoryError:内存不足
    • StackOverflowError:栈溢出(如无限递归)
  • ⚠️ 程序一般不捕获 Error,也无法恢复。

2. Exception(异常)

  • 表示程序可以处理的异常情况。

  • 分为两类:检查异常(Checked)和未检查异常(Unchecked)

  • 检查异常 vs 未检查异常

对比项 检查异常(Checked) 未检查异常(Unchecked)
编译器是否强制处理 ✅ 是(必须 try-catch 或 throws) ❌ 否
继承自 Exception(非 RuntimeException) RuntimeException
表示问题类型 外部环境问题(如文件不存在、网络断开) 程序逻辑错误(如空指针、越界)
是否应该捕获 ✅ 应该处理或提示用户 ❌ 应通过代码避免,而不是捕获
常见例子 FileNotFoundException, SQLException,IOException NullPointerException, IllegalArgumentException

3、异常处理的 5 个关键字

关键字 作用
try 包裹可能出错的代码
catch 捕获并处理特定类型的异常
finally 无论是否出错都执行(用于资源清理)
throw 主动抛出一个异常
throws 声明方法可能抛出的异常类型

异常处理流程图

csharp 复制代码
       try
        ↓
   执行业务代码
        ↓
    是否发生异常?
       ↙     ↘
    是       否
    ↓         ↓
 catch块     finally块
    ↓         ↓
 处理异常   清理资源
    ↓         ↓
       方法结束

示例代码:

java 复制代码
    // 方法1:使用try-catch处理异常
    private void readFileWithTryCatch(String filename) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(filename);
            int data = fis.read();
            appendOutput("   读取到的数据: " + data);
        } catch (FileNotFoundException e) {
            appendOutput("   文件未找到异常: " + e.getMessage());
        } catch (IOException e) {
            appendOutput("   IO异常: " + e.getMessage());
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                    appendOutput("   文件流已关闭");
                } catch (IOException e) {
                    appendOutput("   关闭文件流时发生错误: " + e.getMessage());
                }
            }
        }
    }
    ```
// 方法2:使用throws声明异常
    private void readFileWithThrows(String filename) throws IOException {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(filename);
            int data = fis.read();
            appendOutput("   读取到的数据: " + data);
        } finally {
            if (fis != null) {
                fis.close();
                appendOutput("   文件流已关闭");
            }
        }
    }
    
    // 方法3:问题示例 - 同时使用throws和catch
    private void readFileQuestionable(String filename) throws IOException {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(filename);
            int data = fis.read();
            appendOutput("   读取到的数据: " + data);
        } catch (FileNotFoundException e) {
            appendOutput("   文件未找到异常: " + e.getMessage());
            // 这里捕获了异常但没有重新抛出,调用者不知道发生了异常
        } catch (IOException e) {
            appendOutput("   IO异常: " + e.getMessage());
            // 这里捕获了异常但没有重新抛出,调用者不知道发生了异常
        } finally {
            if (fis != null) {
                fis.close(); // 释放资源,可能抛出IOException异常
                appendOutput("   文件流已关闭");
            }
        }
    }
    
    // 方法4:使用try-with-resources(推荐方式)
    ✅ Java 7+ 推荐使用 **try-with-resources** 简化资源管理
    private void readFileWithTryWithResources(String filename) {
        try (FileInputStream fis = new FileInputStream(filename)) {
            int data = fis.read();
            appendOutput("   读取到的数据: " + data);
        } catch (FileNotFoundException e) {
            appendOutput("   文件未找到异常: " + e.getMessage());
        } catch (IOException e) {
            appendOutput("   IO异常: " + e.getMessage());
        }
        // 不需要finally块,资源会自动关闭
        appendOutput("   文件流已自动关闭");
    }

4、常见的异常类型

未检查异常(RuntimeException)

异常 原因
NullPointerException 访问 null 对象的字段或方法
ArrayIndexOutOfBoundsException 数组下标越界
ClassCastException 类型转换错误
IllegalArgumentException 传递了非法参数
NumberFormatException 字符串转数字失败
ArithmeticException 除以零等算术错误

检查异常

异常 原因
IOException 输入输出错误(如文件读写)
FileNotFoundException 文件不存在
SQLException 数据库操作错误
ParseException 字符串解析失败(如日期)

5、自定义异常

当 Java 内置异常无法满足需求时,可以自定义异常。

scala 复制代码
// 自定义检查异常
public class InvalidAgeException extends Exception {
    public InvalidAgeException(String message) {
        super(message);
    }
}

// 使用
public void setAge(int age) throws InvalidAgeException {
    if (age < 0 || age > 150) {
        throw new InvalidAgeException("年龄必须在 0-150 之间");
    }
    this.age = age;
}

也可以继承 `RuntimeException` 创建未检查异常。
public class InvalidAgeException extends RuntimeException {
    public InvalidAgeException(String message) {
        super(message);
    }
}

public void setAge(int age) {  // 不需要 throws
    if (age < 0 || age > 150) {
        throw new InvalidAgeException("年龄必须在 0-150 之间");
    }
    this.age = age;
}

7、最佳实践与建议

  • 优先使用 try-with-resources 管理资源(如流、连接)
  • 不要捕获所有异常 (避免 catch (Exception e)
  • 不要忽略异常 (避免空的 catch 块)
  • 记录异常日志 (使用 log.error("msg", e)
  • 不要在 finally 中使用 return(会覆盖 try 的返回值)
  • 未检查异常应通过代码预防,而不是捕获
  • 检查异常应合理处理或向上抛出

8、 finally中使用return会怎么样?

  • 结论先行:不要在 finally 中使用 return,虽然语法上允许,但 finally 中使用 return 是非常危险且不推荐的,原因如下:

    • 如果 finally 块中有 return 语句,会覆盖 trycatch 中的返回值
    • 会吞掉异常 (如果 catch 中抛出异常,finallyreturn 会让异常消失)
    • 破坏程序逻辑,难以调试

示例代码

typescript 复制代码
//1.覆盖try中的return
public static String test() {
    try {
        System.out.println("try block");
        return "from try";
    } finally {
        System.out.println("finally block");
        return "from finally";
    }
}

public static void main(String[] args) {
    System.out.println(test());
}

/*
 `finally` 中的 `return`直接结束方法,返回 "from finally",原本 `try` 中要返回的 `"from try"` 被丢弃
输出结果:
try block
finally block
from finally
*/

//2.吞掉try中的异常
public static String badExample() {
    try {
        throw new RuntimeException("error");
    } finally {
        return "success"; // 异常被吞了!
    }
}
这个方法不会抛出异常,而是返回 `"success"`,严重掩盖了错误!

//3.特殊情况:try 中有 return,finally 中修改了返回值(针对引用类型)
public static StringBuilder testRef() {
    StringBuilder sb = new StringBuilder("hello");
    try {
        return sb;
    } finally {
        sb.append(", world"); // 修改对象内容
        // 注意:这里没有 return 新值
    }
}
返回值仍然是 `sb`,但内容被修改了,最终结果是 `"hello, world"`。

正确做法:finally 用于清理资源,不要 return

typescript 复制代码
public static String goodExample() {
    String result = "default";
    try {
        result = "from try";
        return result;
    } finally {
        // 只做资源清理,不要 return
        System.out.println("cleanup resources");
        // ✅ 正确:关闭文件、连接等
        // resource.close();
    }
}
此时返回值是 `"from try"`,`finally` 不影响返回结果。

9、 finally中的代码一定会执行吗?

答案不一定,以前面试美团还是阿里问过,记不清了,finally 块中的代码几乎总是会执行 ,这是 Java 异常处理机制的核心设计之一。它的主要目的就是确保关键的清理操作(如关闭文件、释放资源、断开连接等)能够被执行。

finally 一定会执行的常见情况

示例 :

csharp 复制代码
//1.try 中有 return
public static String test() {
    try {
        return "try";
    } finally {
        System.out.println("finally 执行了");
    }
}
//输出:`finally 执行了` → 然后返回 `"try"`

//2.try 中抛出异常
try {
    throw new RuntimeException("error");
} finally {
    System.out.println("finally 还是执行了");
}

//输出:`finally 还是执行了` → 然后抛出异常

finally 不会执行的几种特殊情况

尽管 finally 的设计目标是"无论如何都要执行",但在以下极少数情况下,它不会执行

JVM 退出或崩溃

如果在 trycatch 中调用了 System.exit(0)Runtime.getRuntime().halt(),JVM 直接终止,finally 不会执行。

csharp 复制代码
try {
    System.out.println("try");
    System.exit(0); // JVM 退出
} finally {
    System.out.println("finally"); // ❌ 不会执行!
}

线程在 try/catch 中被强制终止

  • 如果线程在 trycatch 中被 Thread.stop()(已废弃)或 interrupt() 强行中断,且导致 JVM 不正常运行,finally 可能不会执行。
  • 虽然 interrupt() 本身不会阻止 finally 执行,但如果线程状态异常,可能影响执行。

3. JVM 崩溃或操作系统故障

  • 如发生严重错误(OutOfMemoryErrorStackOverflowError 等)导致 JVM 崩溃。
  • 或操作系统死机、断电等硬件/系统级故障。
csharp 复制代码
try {
    while (true) {
        // 无限循环占满 CPU,可能导致系统无响应
    }
} finally {
    System.out.println("finally"); // ❌ 可能永远不执行
}

在 finally 之前发生 native 代码死锁或无限循环

  • 如果 try 块中调用了 native 方法(JNI),并且该方法陷入死锁或无限循环,JVM 无法继续执行 finally

finally 块在绝大多数情况下都会执行 ,这是它设计的初衷。finally 的承诺是:"只要 JVM 还活着,并且程序能继续运行,我就会执行。

9、什么是 try-with-resources?

try-with-resources 是一种特殊的 try 语句,它允许你声明一个或多个资源 ,并在 try 块执行完毕后自动关闭这些资源,无论是否发生异常。

核心目标:自动、安全地释放资源(如文件流、网络连接、数据库连接等)。

基本语法

csharp 复制代码
try (资源声明) {
    // 使用资源
} catch (ExceptionType e) {
    // 处理异常(可选)
} finally {
    // 清理代码(可选)
}

前提条件:资源必须实现 AutoCloseable 接口

Java 中常见的可关闭资源都实现了 AutoCloseable 或其子接口 Closeable

  • InputStream / OutputStream
  • Reader / Writer
  • Socket
  • Connection(数据库连接)
  • Statement / ResultSet

示例对比:传统方式 vs try-with-resources

ini 复制代码
//1.传统方式(繁琐且易出错)
FileInputStream fis = null;
try {
    fis = new FileInputStream("data.txt");
    int data = fis.read();
    System.out.println(data);
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (fis != null) {
        try {
            fis.close(); // 必须手动关闭,且 close() 也可能抛异常
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
//问题:代码冗长,容易忘记关闭资源,`close()` 异常需要额外处理

//2. ✅ 使用 try-with-resources(简洁安全)
try (FileInputStream fis = new FileInputStream("data.txt")) {
    int data = fis.read();
    System.out.println(data);
} catch (IOException e) {
    e.printStackTrace();
}
//无需手动调用 `close()` ,JVM 会自动调用!

//3.多个资源的使用
try (
    FileInputStream fis = new FileInputStream("input.txt");
    FileOutputStream fos = new FileOutputStream("output.txt")
) {
    int data;
    while ((data = fis.read()) != -1) {
        fos.write(data);
    }
} catch (IOException e) {
    e.printStackTrace();
}
//多个资源用分号 `;` 分隔,关闭顺序是**声明的逆序**(先 `fos`,再 `fis`)。

资源是如何自动关闭的?

  1. try 块执行完毕(正常结束或抛出异常)
  2. JVM 自动调用每个资源的 close() 方法
  3. 关闭顺序:从最后一个声明的资源开始,逆序关闭
  4. 如果 close() 抛出异常,它会被抑制(suppressed) ,主异常(try块中的异常)仍然会被抛出

异常处理机制(重要!)

如果 try 块和 close() 都抛出异常,主异常是 try 块中的异常close() 的异常会被"抑制",但可以通过 getSuppressed() 获取。

php 复制代码
try (MyResource res = new MyResource()) {
    throw new RuntimeException("try 块异常");
} catch (Exception e) {
    System.out.println("主异常: " + e.getMessage());
    for (Throwable t : e.getSuppressed()) {
        System.out.println("被抑制的异常: " + t.getMessage());
    }
}

自定义可关闭资源

你可以创建自己的 AutoCloseable 资源:

csharp 复制代码
public class MyResource implements AutoCloseable {
    public MyResource() {
        System.out.println("资源打开");
    }

    public void doWork() {
        System.out.println("执行任务");
    }

    @Override
    public void close() throws Exception {
        System.out.println("资源关闭");
    }
}

// 使用
try (MyResource res = new MyResource()) {
    res.doWork();
}


输出:
资源打开
执行任务
资源关闭

总结

项目 说明
目的 自动管理资源,避免资源泄漏
要求 资源必须实现 AutoCloseableCloseable
优点 代码简洁、安全、不易出错
适用场景 所有需要手动关闭的资源(I/O、数据库、网络等)
Java 版本 Java 7+

💡 一句话理解
try-with-resources 就像是"用完即扔 "的智能容器------你只管使用资源,JVM 会在 try 块结束后自动帮你关闭它,无论是否发生异常。

相关推荐
舒一笑9 分钟前
如何优雅统计知识库文件个数与子集下不同文件夹文件个数
后端·mysql·程序员
IT果果日记10 分钟前
flink+dolphinscheduler+dinky打造自动化数仓平台
大数据·后端·flink
Java技术小馆22 分钟前
InheritableThreadLoca90%开发者踩过的坑
后端·面试·github
寒士obj31 分钟前
Spring容器Bean的创建流程
java·后端·spring
掉鱼的猫43 分钟前
Spring AOP 与 Solon AOP 有什么区别?
java·spring
不是光头 强1 小时前
axure chrome 浏览器插件的使用
java·chrome
笨蛋不要掉眼泪1 小时前
Spring Boot集成腾讯云人脸识别实现智能小区门禁系统
java·数据库·spring boot
桃源学社(接毕设)1 小时前
云计算下数据隐私保护系统的设计与实现(LW+源码+讲解+部署)
java·云计算·毕业设计·swing·隐私保护
数字人直播1 小时前
视频号数字人直播带货,青否数字人提供全套解决方案!
前端·javascript·后端
shark_chili2 小时前
提升Java开发效率的秘密武器:Jadx反编译工具详解
后端