源本科技 | 码上会

Java 中的 throw 与 throws

2026/01/22
48
0

学习目标

  • 理解 throw 用于主动抛出异常对象,而 throws 用于声明方法可能抛出的异常类型

  • 掌握 throw 的使用场景(如自定义校验、手动触发异常)

  • 掌握 throws 在方法签名中的作用及其对调用者的影响

  • 能够正确处理检查型异常(Checked Exception)的编译期约束

  • 区分 throwthrows 的语法位置、功能和适用范围


主动抛出异常

throw 是一个语句,用于在代码中显式抛出一个异常对象。该对象必须是 Throwable 类或其子类(如 ExceptionRuntimeException)的实例。

throw new ExceptionType("错误描述");

示例 1:手动抛出算术异常

public class ThrowExample {
    public static void main(String[] args) {
        int numerator = 10;
        int denominator = 0;

        if (denominator == 0) {
            throw new ArithmeticException("除数不能为零!");
        }
        System.out.println("结果: " + (numerator / denominator));
    }
}

输出

Exception in thread "main" java.lang.ArithmeticException: 除数不能为零!
    at ThrowExample.main(ThrowExample.java:6)

关键点

  • throw立即中断当前方法执行

  • 控制流跳转到最近的匹配 catch 块;若无匹配,则由 JVM 默认处理器终止程序


示例 2:捕获后重新抛出

class RethrowDemo {
    static void validateAge(int age) {
        try {
            if (age < 0) {
                throw new IllegalArgumentException("年龄不能为负数");
            }
            System.out.println("年龄有效: " + age);
        } catch (IllegalArgumentException e) {
            System.out.println("在 validateAge 中捕获异常");
            throw e; // 重新抛出,交由上层处理
        }
    }

    public static void main(String[] args) {
        try {
            validateAge(-5);
        } catch (IllegalArgumentException e) {
            System.out.println("在 main 中处理异常: " + e.getMessage());
        }
    }
}

输出

在 validateAge 中捕获异常
在 main 中处理异常: 年龄不能为负数

用途:在中间层记录日志或部分处理后,将异常传递给调用者做最终决策。


声明方法可能抛出的异常

throws 是方法签名的一部分,用于告知调用者该方法可能抛出哪些检查型异常,从而将异常处理责任委托给调用方

返回类型 方法名(参数列表) throws 异常类型1, 异常类型2 { ... }

示例 1

未处理检查型异常导致编译错误

public class CompileErrorExample {
    public static void main(String[] args) {
        Thread.sleep(1000); // 编译错误!
        System.out.println("等待结束");
    }
}

编译错误

error: unreported exception InterruptedException; must be caught or declared to be thrown

原因:Thread.sleep() 声明了 throws InterruptedException,属于检查型异常,必须处理。


示例 2

使用 throws 委托异常处理

public class ThrowsExample {
    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(1000); // 不再需要 try-catch
        System.out.println("等待 1 秒后继续");
    }
}

输出

等待 1 秒后继续

此时异常处理责任被转移给 JVM(因为 main 是程序入口),JVM 会终止程序并打印堆栈(若发生异常)。


示例 3

自定义方法声明 throws

import java.io.*;

class FileProcessor {
    // 声明该方法可能抛出 IOException
    static void readFile(String path) throws IOException {
        FileReader reader = new FileReader(path);
        System.out.println("文件打开成功");
        reader.close();
    }

    public static void main(String[] args) {
        try {
            readFile("missing.txt");
        } catch (IOException e) {
            System.out.println("文件操作失败: " + e.getMessage());
        }
    }
}

输出(若文件不存在):

文件操作失败: missing.txt (No such file or directory)

规则:调用声明了 throws 检查型异常的方法时,调用者必须:

  • 使用 try-catch 捕获,

  • 在自身方法签名中继续声明 throws


throwthrows

特性

throw

throws

类型

语句(Statement)

方法修饰符(Declaration)

位置

方法体内部

方法签名末尾

作用

抛出具体的异常对象

声明可能抛出的异常类型

异常数量

一次只能抛出一个异常

可声明多个异常(逗号分隔)

适用异常

可抛出检查型或非检查型异常

主要用于检查型异常(非检查型无需声明)

执行影响

立即中断当前流程

不中断执行,仅声明风险

示例

throw new IllegalArgumentException("无效输入");

void connect() throws IOException, TimeoutException


最佳实践

  1. 优先使用标准异常(如 IllegalArgumentExceptionIllegalStateException),而非随意创建新异常。

  2. 在方法入口处校验参数,使用 throw 提前失败(Fail-Fast 原则)。

  3. 避免在 throws 中声明过多异常,可考虑封装为统一业务异常。

  4. 不要忽略检查型异常:即使暂时无法处理,也应记录日志或转换为运行时异常(谨慎使用)。

  5. 自定义异常时继承合适的父类

    • 业务逻辑错误 → 继承 RuntimeException(非检查型)

    • I/O 或外部依赖错误 → 继承 Exception(检查型)


重点总结

  • throw 用于主动抛出异常实例,立即中断程序流。

  • throws 用于声明方法可能抛出的检查型异常,将处理责任转移给调用者。

  • 检查型异常必须在编译期处理(try-catchthrows),非检查型异常则可选。

  • 合理使用这两个关键字,可提升代码的健壮性可读性可维护性


思考题

  1. 如果一个方法声明了 throws IOException,但内部并未抛出任何异常,这样写是否合法?有何影响?

  2. 能否在 catch 块中使用 throws?为什么?

  3. 如何设计一个自定义业务异常 InvalidOrderException,并在订单验证服务中使用 throwthrows