Java 异常机制是一种"分离正常逻辑与错误处理"的编程方式,合理使用可以让程序更健壮、更易维护。核心机制 :
try-catch-finally和throws
Java 异常的继承体系
Java 中所有异常和错误都继承自 Throwable 类。其主要分为Error 和Exception两大类:异常是"可恢复的问题",错误是"不可恢复的灾难"
            
            
              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语句,会覆盖try和catch中的返回值
- 会吞掉异常 (如果 catch中抛出异常,finally的return会让异常消失)
- 破坏程序逻辑,难以调试
 
- 如果 
示例代码
            
            
              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 退出或崩溃
如果在 try 或 catch 中调用了 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 中被强制终止
- 如果线程在 try或catch中被Thread.stop()(已废弃)或interrupt()强行中断,且导致 JVM 不正常运行,finally可能不会执行。
- 虽然 interrupt()本身不会阻止finally执行,但如果线程状态异常,可能影响执行。
3. JVM 崩溃或操作系统故障
- 如发生严重错误(OutOfMemoryError、StackOverflowError等)导致 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`)。资源是如何自动关闭的?
- try块执行完毕(正常结束或抛出异常)
- JVM 自动调用每个资源的 close()方法
- 关闭顺序:从最后一个声明的资源开始,逆序关闭
- 如果 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();
}
输出:
资源打开
执行任务
资源关闭总结
| 项目 | 说明 | 
|---|---|
| 目的 | 自动管理资源,避免资源泄漏 | 
| 要求 | 资源必须实现 AutoCloseable或Closeable | 
| 优点 | 代码简洁、安全、不易出错 | 
| 适用场景 | 所有需要手动关闭的资源(I/O、数据库、网络等) | 
| Java 版本 | Java 7+ | 
💡 一句话理解 :
try-with-resources就像是"用完即扔 "的智能容器------你只管使用资源,JVM 会在try块结束后自动帮你关闭它,无论是否发生异常。