杰瑞科技汇

java throw和throws

核心思想(一句话总结)

  • throws声明一个方法可能会抛出某种异常,它把这个“抛出异常”的责任交给了方法的调用者
  • throw执行一个动作,真正地创建并抛出一个异常对象,它是在代码中主动制造一个异常。

throws 关键字

throws 用在方法签名中,用来声明该方法可能会抛出的一种或多种异常类型,它是一个“预告”,告诉调用者:“我在执行过程中可能会遇到这些麻烦,你需要准备好处理它们。”

java throw和throws-图1
(图片来源网络,侵删)

语法

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

特点

  1. 声明责任throws 的作用是声明,而不是处理,它告诉编译器这个方法可能会抛出哪些异常。
  2. 转移责任:它将处理异常的责任从当前方法转移到了调用该方法的方法上。
  3. 用于方法签名:它只能出现在方法签名后面。
  4. 受检异常throws 主要用于处理受检异常,如果你不处理(try-catch)也不声明(throws),编译器会报错。
  5. 链式传递:如果一个方法 A 调用了另一个方法 B,而方法 B 声明了 throws Exception,那么方法 A 必须要么 try-catch 这个异常,要么自己也声明 throws Exception

示例

假设我们有一个方法,它从一个文件中读取内容,文件操作可能会抛出 FileNotFoundException(文件没找到)和 IOException(读写错误),这两个都是受检异常。

import java.io.FileInputStream;
import java.io.IOException;
public class FileReaderExample {
    // 这个方法声明它可能会抛出 IOException
    // 它自己不处理,而是让调用者来处理
    public void readFile(String filePath) throws IOException {
        System.out.println("准备读取文件: " + filePath);
        FileInputStream fis = new FileInputStream(filePath);
        // ... 其他文件操作代码 ...
        System.out.println("文件读取成功!");
    }
}

我们来看调用它的代码:

public class Main {
    public static void main(String[] args) {
        FileReaderExample example = new FileReaderExample();
        // 因为 readFile() 方法声明了 throws IOException,
        // main 方法必须处理这个异常
        try {
            example.readFile("my_file.txt");
        } catch (IOException e) {
            // 捕获并处理异常
            System.err.println("读取文件时出错: " + e.getMessage());
            e.printStackTrace();
        }
        System.out.println("程序继续执行...");
    }
}

在这个例子中:

  • readFile 方法使用 throws IOException 来声明它可能会发生 IO 错误。
  • main 方法作为调用者,通过 try-catch 块来捕获并处理这个潜在的错误。

throw 关键字

throw 用于在方法体内手动地抛出一个异常对象,当你明确知道某个错误条件已经发生,并且想让程序以异常的方式处理时,就可以使用 throw

java throw和throws-图2
(图片来源网络,侵删)

语法

throw new 异常类名(参数);
  • new 异常类名(参数):创建一个异常对象,参数通常是错误信息字符串。
  • throw:关键字,用于抛出这个对象。

特点

  1. 执行动作throw 是一条可执行的语句,它会立即中断当前方法的执行流程,寻找匹配的 catch 块或者向上传递异常。
  2. 创建异常:它负责创建一个异常实例并抛出。
  3. 用于方法体内:它只能用在方法体内部。
  4. 任何异常类型:无论是受检异常还是非受检异常(RuntimeException 及其子类),都可以被 throw 抛出。
  5. 主动触发:通常用于根据程序的业务逻辑主动抛出异常,检查参数是否合法,如果不合法就抛出 IllegalArgumentException

示例

假设我们有一个方法,用于计算一个数的倒数,我们不能计算 0 的倒数,因为这是数学上无意义的操作,当参数为 0 时,我们应该抛出一个异常。

public class Divider {
    /**
     * 计算一个数的倒数
     * @param number 输入的数字
     * @return 倒数
     * @throws IllegalArgumentException 如果输入数字为 0
     */
    public double calculateReciprocal(int number) {
        // 业务逻辑检查:如果数字为 0,则抛出异常
        if (number == 0) {
            // 使用 throw 主动创建并抛出一个异常
            throw new IllegalArgumentException("参数不能为 0,因为 0 没有倒数。");
        }
        return 1.0 / number;
    }
}

调用这个方法的代码:

public class Main {
    public static void main(String[] args) {
        Divider divider = new Divider();
        try {
            System.out.println("10 的倒数是: " + divider.calculateReciprocal(10));
            System.out.println("0 的倒数是: " + divider.calculateReciprocal(0)); // 这行代码会抛出异常
        } catch (IllegalArgumentException e) {
            // 捕获由 throw 抛出的异常
            System.err.println("捕获到非法参数异常: " + e.getMessage());
        }
        System.out.println("程序继续执行...");
    }
}

输出结果:

10 的倒数是: 0.1
捕获到非法参数异常: 参数不能为 0,因为 0 没有倒数。
程序继续执行...

在这个例子中:

java throw和throws-图3
(图片来源网络,侵删)
  • calculateReciprocal 方法内部使用 throw new IllegalArgumentException(...) 来主动抛出一个异常。
  • 这个异常被 main 方法中的 try-catch 块捕获并处理。

总结与对比

特性 throws throw
含义 声明、预告 执行、抛出
语法位置 方法签名后 方法体内部
作用 告知调用者此方法可能抛出的异常类型,将处理责任转移 手动创建并抛出一个异常对象,中断当前方法执行
与异常的关系 与异常类型关联 与异常对象关联
使用场景 处理可能由 JVM 或 API 抛出的受检异常 根据业务逻辑主动抛出异常,可以是受检的也可以是非受检的
例子 public void method() throws IOException {} if (age < 0) throw new IllegalArgumentException("年龄不能为负");

一个综合示例

让我们把 throwthrows 放在同一个例子中,看看它们如何协同工作。

public class UserService {
    /**
     * 根据用户ID查找用户
     * @param userId 用户ID
     * @return 用户对象
     * @throws UserNotFoundException 如果用户不存在
     */
    public User findUserById(int userId) throws UserNotFoundException {
        // 模拟从数据库查询用户
        if (userId <= 0) {
            // 主动抛出一个异常,这个异常是自定义的,通常是受检异常
            throw new UserNotFoundException("无效的用户ID: " + userId);
        }
        // ... 查询逻辑 ...
        User user = new User(userId, "John Doe");
        if (user == null) {
            throw new UserNotFoundException("未找到ID为 " + userId + " 的用户。");
        }
        return user;
    }
}
// 自定义异常类
class UserNotFoundException extends Exception {
    public UserNotFoundException(String message) {
        super(message);
    }
}
// 调用方
public class Main {
    public static void main(String[] args) {
        UserService userService = new UserService();
        try {
            // 1. 调用 findUserById,它声明了 throws UserNotFoundException
            User user = userService.findUserById(-1); // 传入一个无效ID
            System.out.println("找到用户: " + user.getName());
        } catch (UserNotFoundException e) {
            // 2. 捕获由 findUserById 方法内部 throw 抛出的异常
            System.err.println("用户查询失败: " + e.getMessage());
        }
        System.out.println("程序结束。");
    }
}

流程分析:

  1. main 方法调用 userService.findUserById(-1)
  2. findUserById 方法执行,发现 userId 为 -1,不合法。
  3. 方法执行 throw new UserNotFoundException(...),创建了一个异常对象并抛出。
  4. 由于 findUserById 方法在签名上声明了 throws UserNotFoundException,它将这个异常传递给了调用者 main 方法。
  5. main 方法的 catch 块捕获了这个 UserNotFoundException,并打印出错误信息。
  6. 程序继续执行,没有崩溃。

希望这个详细的解释和例子能帮助你彻底理解 throwthrows 的区别!

分享:
扫描分享到社交APP
上一篇
下一篇