Java 异常处理

发布于 2026-07-30 20:22 更新于 2026-07-30 20:22 1698 字 9 min read ... 访问量

本文系统介绍了Java异常处理机制,包括异常体系结构(Error与Exception的分类)、try-catch-finally和try-with-resources的使用方法、throws与throw的区别,以及自定义异常的场景与建议。强调异常应用于真实可处理的场景,避免盲目捕获和忽略,需保留异常栈信息并提供清晰的错误反馈,同时推荐使用try-with-resources自动管理资源,提升代码安全性和可维护性。

Java 异常处理

异常处理用于描述、传递和处理程序执行期间出现的异常情况。合理使用异常可以帮助定位错误,并在可恢复的场景下采取补救措施。

异常机制不能替代正常的业务判断,也不能保证程序在任何情况下都继续运行。

一、异常体系

image-001
image-001

Java 中所有可以被抛出的对象都继承自 Throwable。它主要分为 ErrorException 两大类。

Error

Error 通常表示 JVM 或运行环境中的严重问题,例如 OutOfMemoryError。这类问题往往不是普通业务代码能够恢复的,因此一般不在业务代码中捕获后继续执行。

Exception

Exception 表示程序可能处理的异常情况,可以继续分为运行时异常和受检查异常。

运行时异常

RuntimeException 及其子类属于运行时异常。编译器不强制要求捕获或声明,常见类型包括:

  • NullPointerException
  • ArrayIndexOutOfBoundsException
  • ClassCastException
  • ArithmeticException
  • IllegalArgumentException

运行时异常通常与参数不合法、对象状态错误或程序逻辑缺陷有关,应优先通过校验和修正代码来避免,而不是依赖大量 catch 隐藏问题。

受检查异常

RuntimeException 及其子类之外的 Exception 通常属于受检查异常,例如 IOException

编译器要求程序采用以下方式之一:

  • 使用 try-catch 捕获。
  • 使用 throws 声明并继续向调用者传播。

“受检查”发生在编译阶段,但异常对象是在程序运行到相关语句时才可能产生。

二、使用 try-catch-finally

基本语法

try {
    // 可能出现异常的代码
} catch (SpecificException e) {
    // 处理特定异常
} catch (Exception e) {
    // 处理其他异常
} finally {
    // 通常用于释放资源或执行收尾操作
}

执行规则如下:

  1. try 中没有抛出异常时,不会执行对应的 catch

  2. try 中抛出异常时,JVM 会从上到下寻找第一个类型匹配的 catch

  3. 较具体的子类异常必须写在较宽泛的父类异常之前,否则后面的分支无法到达。

  4. finally 通常都会执行,但 JVM 被强制终止、进程崩溃或执行 System.exit() 等特殊情况除外。

示例

import java.util.InputMismatchException;
import java.util.Scanner;

public class ExceptionDemo {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        try {
            int i = scanner.nextInt();
            int j = scanner.nextInt();

            int result = i / j;
            System.out.println("相除结果:" + result);

            int[] arr = new int[5];
            System.out.println(arr[i]);
            System.out.println("计算完毕");
        } catch (ArithmeticException e) {
            System.out.println("除数不能为零");
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("数组索引越界");
        } catch (InputMismatchException e) {
            System.out.println("请输入整数");
        } catch (Exception e) {
            System.out.println("程序出现其他异常");
            e.printStackTrace();
        } finally {
            scanner.close();
            System.out.println("输入资源已关闭");
        }

        System.out.println("程序继续执行");
    }
}

在实际项目中,不应只输出模糊提示后忽略异常。通常需要记录异常栈、返回明确错误信息,或将异常转换为当前业务层能够理解的类型。

三、使用 try-with-resources

try-with-resources 从 Java 7 开始提供,用于自动关闭资源。资源对象必须实现 AutoCloseable 接口。

传统写法

import java.io.FileInputStream;
import java.io.IOException;

public class ResourceDemo {

    public static void main(String[] args) {
        FileInputStream input = null;

        try {
            input = new FileInputStream("d:/a.txt");
            System.out.println(input.read());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (input != null) {
                try {
                    input.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

关闭前必须进行空值判断,否则创建资源失败时,finally 中可能再次出现 NullPointerException

推荐写法

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class CopyFileDemo {

    public static void main(String[] args) {
        try (
                FileInputStream input = new FileInputStream("d:/a.txt");
                FileOutputStream output = new FileOutputStream("d:/b.txt")
        ) {
            input.transferTo(output);
        } catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println("程序继续执行");
    }
}

注意事项:

  • try 圆括号中的资源必须实现 AutoCloseable
  • 声明多个资源时,关闭顺序与声明顺序相反。
  • 资源变量不能在 try 块中重新赋值。
  • 从 Java 9 开始,也可以使用在外部声明且满足“实际不可变”条件的资源变量。
  • 如果业务代码和 close() 同时抛出异常,关闭资源时产生的异常会作为受抑制异常保存,可通过 getSuppressed() 获取。

四、使用 throws 声明异常

throws 写在方法或构造器声明之后,表示当前方法不在这里捕获该异常,而是允许异常继续向调用者传播。

import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class FileService {

    public FileInputStream openFile(String path) throws FileNotFoundException {
        return new FileInputStream(path);
    }
}

throws 本身并没有处理异常。调用者仍然需要捕获该异常,或者继续使用 throws 向上声明。

方法可以声明多个异常:

public void execute() throws FirstException, SecondException {
    // 方法体
}

若声明了父类异常,通常不必再重复声明它的子类异常。

五、使用 throw 抛出异常

throw 用于在方法体中抛出一个具体的异常对象。

public void copy(String sourcePath, String targetPath) {
    if (sourcePath == null || targetPath == null) {
        throw new IllegalArgumentException("源路径和目标路径不能为空");
    }

    // 执行复制操作
}

程序执行到 throw 后,当前代码路径会立即中断,控制权交给能够处理该异常的调用层。

throwthrows 的区别

关键字使用位置作用
throw方法体内部抛出一个具体异常对象
throws方法或构造器声明后声明可能向调用者传播的异常类型

六、自定义异常

当 JDK 提供的异常类型无法清楚表达业务含义时,可以定义业务异常。

自定义受检查异常

public class InvalidEmailAddressException extends Exception {

    public InvalidEmailAddressException(String message) {
        super(message);
    }
}
public class EmailManager {

    public void sendEmail(
            String recipient,
            String title,
            String content
    ) throws InvalidEmailAddressException {
        if (recipient == null || recipient.isBlank()) {
            throw new InvalidEmailAddressException("收件人地址不能为空");
        }

        System.out.println("发送邮件成功");
    }
}

继承 Exception 后,调用者必须捕获或声明该异常。

自定义运行时异常

若异常表示参数错误、业务规则不满足,且不希望强制每一层代码显式捕获,也可以继承 RuntimeException

public class InvalidEmailAddressException extends RuntimeException {

    public InvalidEmailAddressException(String message) {
        super(message);
    }
}

选择受检查异常还是运行时异常,应结合调用者是否能够恢复、项目异常处理规范以及框架约定决定。

七、异常处理建议

  • 捕获能够真正处理的异常,不要无意义地捕获后忽略。
  • 优先捕获具体异常,避免直接使用过于宽泛的 Exception
  • 保留原始异常作为 cause,不要丢失问题根因。
  • 不要把异常用于普通流程控制。
  • 资源优先使用 try-with-resources 管理。
  • 对外返回稳定的错误信息,对内记录完整异常栈和必要上下文。

喜欢的话,留下你的评论吧~

... 访问量
© 2026 跨越星轨的客 @Hoshiumi
Powered by theme astro-koharu · Inspired by Shoka