Skip to content

异常处理机制

前置知识

在阅读本章前,你需要了解:Java 基础语法、方法调用和基本的面向对象概念。

为什么需要异常处理?

想象一下,你正在写一个文件读取程序,突然文件不存在了,程序直接崩溃了。是不是很糟糕?这时,异常处理机制就像是给你的程序装上了“安全带”和“减震器”,它帮你优雅地应对意外情况,让程序有机会补救或者至少优雅失败。

异常处理就是告诉 Java:这里可能出问题,但我已经准备好了应对措施。这样你的代码才更健壮,也更容易维护。

具体章节

1. try-catch-finally 的基本使用

先说说概念吧:

  • try:放可能出问题的代码。
  • catch:出了异常后怎么办。
  • finally:无论是否发生异常,这块代码都必须执行,通常用来清理资源。

为什么它们分开写呢?

这是为了让程序流程清晰,分出“正常流程”和“异常处理”的部分。


最简单的例子:捕获并处理异常

java
import java.util.Scanner;

public class SimpleTryCatch {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入一个整数: ");
        try {
            int number = scanner.nextInt(); // 可能抛出 InputMismatchException
            System.out.println("你输入的数字是:" + number);
        } catch (Exception e) {
            System.out.println("输入有误,请输入整数!");
        } finally {
            scanner.close(); // 保证资源关闭
            System.out.println("输入结束,资源已关闭");
        }
    }
}

这段代码做了什么?

  1. 进入 try 块尝试读取一个整数。
  2. 如果输入不合法,nextInt() 抛出异常,程序跳转到 catch 块打印错误提示。
  3. 无论是否异常,finally 块都会执行,关闭 Scanner 资源并提示结束。

2. 多重 catch 捕获不同异常类型

Java 允许针对不同异常类型写不同的处理逻辑,避免“一刀切”的笼统捕获。


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

public class MultiCatchExample {
    public static void main(String[] args) {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader("test.txt"));
            String firstLine = reader.readLine(); // 可能抛出 IOException
            System.out.println("文件第一行内容:" + firstLine);
        } catch (java.io.FileNotFoundException e) {
            System.out.println("文件未找到,请确认路径是否正确:" + e.getMessage());
        } catch (IOException e) {
            System.out.println("读文件时发生错误:" + e.getMessage());
        } finally {
            try {
                if (reader != null) {
                    reader.close(); // 资源释放
                }
            } catch (IOException e) {
                System.out.println("关闭文件时失败:" + e.getMessage());
            }
            System.out.println("程序执行结束");
        }
    }
}

这段代码做了什么?

  1. try 内打开文件并读取内容。
  2. catch 分别处理“文件找不到”和“读文件出错”两种不同异常。
  3. finally 确保即使异常发生也关闭文件。
  4. finally 中的关闭操作自己也单独捕获异常,防止程序二次崩溃。

3. try-with-resources 自动关闭资源

上面代码的 finally 块写起来挺麻烦,Java 7 引入了 try-with-resources 语法,专门用于自动关闭实现了 AutoCloseable接口的资源,极大简化了代码。


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

public class TryWithResourcesExample {
    public static void main(String[] args) {
        // try-with-resources语法,声明的资源会自动关闭
        try (BufferedReader reader = new BufferedReader(new FileReader("test.txt"))) {
            String firstLine = reader.readLine(); // 读取文件第一行
            System.out.println("文件第一行内容:" + firstLine);
        } catch (IOException e) {
            System.out.println("文件操作出错:" + e.getMessage());
        }
    }
}

这段代码做了什么?

  1. try 声明了一个资源 reader,会自动关闭。
  2. 省去了手动编写 finally 块释放资源。
  3. 简洁、易读,同时更安全,避免忘记关闭资源导致内存泄漏。

常见陷阱 ⚠️

  • catch 过于泛泛:很多新手直接写 catch (Exception e),这样会把所有异常都捕获,有时掩盖了真正的问题。建议具体异常具体捕获。

  • finally 中抛异常finally 里抛出异常会覆盖 trycatch 中的异常,导致调试困难。尽量避免在 finally 抛异常,或者做好捕获。

  • 资源未关闭:尤其是文件、数据库连接等,忘记关闭会导致资源泄漏,用 try-with-resources 绝对是个好习惯。

  • 多个异常处理顺序:捕获异常时,子类异常必须放在超类异常前面,否则编译器报错。


💡 实战建议

  1. 养成用具体异常捕获的习惯,避免捕获顶级 Exception
  2. 对于文件、数据库等资源,优先使用 try-with-resources,简化代码且防止资源泄漏。
  3. 异常处理逻辑中,避免过度捕获和吞掉异常,有时候该抛出就抛出,让调用者决定。
  4. 调试时多关注异常堆栈,准确定位根因。

🔍 深入理解:异常链和自定义异常

有时候我们想要抛出自己的异常,但又不想丢失原始异常信息,可以使用异常链机制:

java
public class BusinessException extends Exception {
    public BusinessException(String message, Throwable cause) {
        super(message, cause); // 保留异常链
    }
}

在捕获异常后:

java
try {
    // 可能出错的代码
} catch(IOException e) {
    throw new BusinessException("业务逻辑异常", e); // 保留原始异常
}

这保证了调试时可以看到完整的异常轨迹。

小结

  • 异常处理让程序更健壮、有恢复力。
  • try-catch-finally 是最基础的异常结构,finally 保证清理资源。
  • 多重 catch 有助于精确定位不同异常。
  • try-with-resources 简化资源关闭,首推用法。
  • 注意捕获顺序和避免过度捕获。

实战应用

假设你在做一个银行转账功能,涉及数据库连接、文件日志写入、网络通信等等。任何一个环节出错,都不应该让整个系统崩溃。通过合理的异常处理:

  • 捕获数据库连接失败,重试或告知用户。
  • 关闭数据库连接和文件句柄,防止内存泄漏。
  • 记录异常日志,告知开发者排查。

这些都体现了异常机制在真实业务中的重要性。


延伸思考

  • 你觉得什么时候适合自己抛出异常,什么时候应该在方法内部处理?
  • 如何设计自定义异常体系,让不同层次之间的异常传递更清晰?
  • 继续学习如何结合日志框架,实现异常的可追踪性。

如果你到这里还不怕异常,我们就成功了一半!让我们动手写代码,调试,感受异常处理的威力吧。