杰瑞科技汇

throw与throws有何区别?

核心区别(一句话总结)

  • throws:用在方法签名上,表示该方法可能会抛出某个异常,但不处理它,而是将处理责任抛给它的调用者。
  • throw:用在方法体内部,是手动创建并抛出一个异常实例的动作。

throws 关键字

throws 的核心作用是声明,它告诉调用者:“我这个方法可能会出问题,可能会抛出异常,但我不想在这里处理,所以你得准备好处理它。”

throw与throws有何区别?-图1
(图片来源网络,侵删)

语法

修饰符 返回值类型 方法名(参数列表) throws 异常类型1, 异常类型2, ... {
    // 方法体
}

特点

  1. 位置:必须跟在方法签名的参数列表之后。
  2. 作用:声明一个方法可能抛出的受检异常(Checked Exception),对于非受检异常(Unchecked Exception,即 RuntimeException 及其子类),编译器不强制要求使用 throws 声明,但也可以使用。
  3. 责任转移:它将处理异常的责任从当前方法转移到了方法的调用者身上。
  4. 编译器检查:如果一个方法声明了受检异常,那么调用这个方法的代码必须处理这个异常(要么用 try-catch 捕获,要么在自己的方法签名上继续使用 throws 声明)。

示例

假设我们有一个方法,它从一个文件中读取内容,由于文件可能不存在,FileInputStream 的构造函数会抛出 FileNotFoundException,这是一个受检异常。

import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class ThrowsExample {
    // 声明这个方法可能会抛出 FileNotFoundException
    // 它不处理这个异常,而是交给调用者处理
    public void readFile(String filePath) throws FileNotFoundException {
        System.out.println("准备读取文件: " + filePath);
        FileInputStream fis = new FileInputStream(filePath); // 这行代码可能抛出 FileNotFoundException
        System.out.println("文件打开成功!");
    }
    public static void main(String[] args) {
        ThrowsExample example = new ThrowsExample();
        // 调用者必须处理 readFile() 方法声明的异常
        try {
            example.readFile("non_existent_file.txt");
        } catch (FileNotFoundException e) {
            // 调用者捕获并处理了异常
            System.err.println("捕获到异常:文件未找到!");
            e.printStackTrace(); // 打印异常堆栈信息
        }
        System.out.println("程序继续执行...");
    }
}

在这个例子中:

  • readFile 方法使用 throws FileNotFoundException 声明了它可能抛出的异常。
  • main 方法作为 readFile 的调用者,必须用 try-catch 块来捕获这个异常,否则编译器会报错。

throw 关键字

throw 的核心作用是执行,它允许程序员在代码的任何地方手动抛出一个异常对象。

语法

throw new 异常类名(参数);

特点

  1. 位置:必须在方法体内部使用。
  2. 作用:主动抛出一个异常实例,这个异常可以是 Java 内置的,也可以是自定义的。
  3. 创建异常对象throw 关键字后面必须跟一个异常对象(new Exception(...)),每次执行 throw 语句,都会创建一个新的异常对象,并立即中断当前方法的正常执行流程,然后沿着调用栈向上寻找匹配的 catch 块。
  4. 抛出何种异常
    • 你可以抛出任何 Throwable 类或其子类的对象。
    • 我们抛出受检异常(Exception 的子类)或非受检异常(RuntimeException 的子类)。

示例

假设我们有一个方法,用于设置一个人的年龄,年龄不能为负数,如果传入负数,我们就认为这是一个非法的参数,应该抛出异常。

throw与throws有何区别?-图2
(图片来源网络,侵删)
public class ThrowExample {
    // 自定义一个异常类,可选,但推荐
    static class InvalidAgeException extends Exception {
        public InvalidAgeException(String message) {
            super(message);
        }
    }
    // 手动抛出异常
    public void setAge(int age) throws InvalidAgeException {
        if (age < 0) {
            // 创建一个异常对象并手动抛出
            // 这会中断 setAge 方法的执行
            throw new InvalidAgeException("年龄不能为负数!你输入的是: " + age);
        }
        System.out.println("年龄设置成功: " + age);
    }
    public static void main(String[] args) {
        ThrowExample example = new ThrowExample();
        try {
            example.setAge(25); // 正常情况
            example.setAge(-5); // 会抛出异常
        } catch (InvalidAgeException e) {
            // 捕获并处理我们手动抛出的异常
            System.err.println("捕获到自定义异常:" + e.getMessage());
            e.printStackTrace();
        }
        System.out.println("程序继续执行...");
    }
}

在这个例子中:

  • setAge 方法内部有一个逻辑判断,当条件不满足时,使用 throw new InvalidAgeException(...) 手动创建并抛出了一个异常。
  • 抛出异常后,setAge 方法中 throw 语句之后的代码(System.out.println...)将不会被执行。
  • main 方法通过 try-catch 捕获了这个异常。

throwsthrow 的关系与对比

为了更清晰地理解,我们用一个表格来对比它们:

特性 throws throw
throws throw
语法 throws ExceptionType throw new ExceptionType()
作用 声明一个方法可能抛出的异常 执行一个操作,抛出一个异常实例
位置 方法签名之后 方法体内部
目的 告知调用者此方法可能产生的异常,将处理责任上交 在程序逻辑中主动中断流程,报告一个错误情况
与异常的关系 它不创建异常对象,只是一个声明 它必须创建一个异常对象并抛出
后续操作 调用者必须处理该异常(try-catch 或继续 throws 抛出后,当前方法立即终止,寻找 catch

何时使用?

使用 throws 的情况:

  1. 处理受检异常:当你的方法内部调用了另一个可能抛出受检异常的方法,而你不想在当前方法中处理它时,就可以在方法签名上使用 throws
  2. 异常传递:当你希望异常能够沿着调用栈向上传递,由更高层的调用者(如 main 方法或控制器层)来统一处理时。
  3. 框架设计:在设计 API 或框架时,有时希望将异常处理的策略留给 API 的使用者来决定。

使用 throw 的情况:

  1. 验证业务逻辑:当方法的输入参数不满足业务规则时(如年龄为负、用户名为空、密码太短等),可以抛出 IllegalArgumentException 或自定义异常。
  2. 主动报告错误:在程序运行过程中,当检测到一个无法继续执行的状态时(如从缓存中获取的数据格式错误),可以手动抛出异常来中断流程。
  3. 实现自定义异常:当你需要抛出非标准的、特定于业务领域的错误时,会先创建一个自定义异常类,然后使用 throw 来抛出它。

最佳实践

  1. 具体化异常:尽量抛出具体的异常类型,而不是笼统的 Exception,抛出 FileNotFoundException 比抛出 IOException 更能清晰地表达问题。
  2. 文档化异常:在 JavaDoc 中使用 @throws 标签来记录方法可能抛出的异常,这能帮助其他开发者更好地使用你的代码。
  3. 不要抑制异常:避免使用空的 catch 块(catch (Exception e) {}),这会隐藏错误信息,使得调试变得非常困难,至少要打印日志或记录异常信息。
  4. 优先使用非受检异常:对于程序内部的逻辑错误(如 NullPointerException, IllegalArgumentException),通常使用 RuntimeException 的子类,因为强制调用者处理这些本应由程序员修复的错误,会增加不必要的代码负担。throws 主要用于处理那些真正由外部环境(如文件、网络)引起的、调用者可能需要做出相应决策的受检异常。

记住这个简单的比喻:

  • throws 就像一个“免责声明”“风险提示”,一个方法(比如蹦极项目)对你说:“这个项目有风险(可能会抛出异常),我们不负责,你自己决定要不要玩(调用者负责处理)。”
  • throw 就像是你自己主动拉下弹射绳的动作,你检查了装备(业务逻辑),发现不安全,于是你主动拉下绳索(抛出异常),中断了整个活动(方法执行)。

理解了这两者的区别,你就能在 Java 编写更健壮、更清晰的代码了。

throw与throws有何区别?-图3
(图片来源网络,侵删)
分享:
扫描分享到社交APP
上一篇
下一篇