|
@@ -1,8 +1,8 @@
|
|
|
# 2026-07-28 异常处理(Exception Handling)基础练习(课堂练习用)
|
|
# 2026-07-28 异常处理(Exception Handling)基础练习(课堂练习用)
|
|
|
|
|
|
|
|
-> **说明**:以下习题围绕今日授课核心内容——**Java 异常处理(Exception Handling)** 展开,涵盖 Throwable 体系结构、try-catch 多 catch 块捕获处理、常见运行时异常(数组下标越界、空指针、类型转换、算术异常等)、异常处理的鲁棒性等知识点。
|
|
|
|
|
|
|
+> **说明**:以下习题围绕今日授课核心内容——**Java 异常处理(Exception Handling)** 展开,涵盖 Throwable 体系结构、try-catch 多 catch 块捕获处理、异常常用方法(getMessage/printStackTrace/getCause)、常见运行时异常(数组下标越界、空指针、类型转换、算术异常等)、throw 与 throws 关键字、自定义异常、异常处理的鲁棒性等知识点。
|
|
|
>
|
|
>
|
|
|
-> 题目分为两部分:**随堂练习**(异常处理基础,6 题)和 **进阶练习**(综合挑战,2 题)。
|
|
|
|
|
|
|
+> 题目分为两部分:**随堂练习**(异常处理基础,8 题)和 **进阶练习**(综合挑战,3 题)。
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
@@ -434,6 +434,246 @@ public static int divide(String s1, String s2) /* 请补充 throws 声明 */ {
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
|
|
+## 练习 7:异常常用方法(getMessage / printStackTrace / getCause)
|
|
|
|
|
+
|
|
|
|
|
+**难度**:⭐⭐
|
|
|
|
|
+**知识点**:异常常用方法、`getMessage()`、`printStackTrace()`、`getCause()`、`fillInStackTrace()`
|
|
|
|
|
+
|
|
|
|
|
+**场景描述**:当 catch 捕获到异常对象后,可以调用 `Throwable` 类提供的各种方法来获取异常的详细信息。本练习要求在不同的异常场景中使用这些方法,理解它们各自的作用。
|
|
|
|
|
+
|
|
|
|
|
+**题目**:完成以下 `ExceptionMethodDemo` 类。
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+import java.io.FileNotFoundException;
|
|
|
|
|
+
|
|
|
|
|
+public class ExceptionMethodDemo {
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 任务 1:体验 getMessage() 和 printStackTrace()
|
|
|
|
|
+ * 传入一个数组和索引,尝试访问该索引的元素
|
|
|
|
|
+ */
|
|
|
|
|
+ public static void accessArray(int[] arr, int index) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ System.out.println("数组元素:" + arr[index]);
|
|
|
|
|
+ } catch (ArrayIndexOutOfBoundsException e) {
|
|
|
|
|
+ // 1. 输出异常的描述信息(getMessage())
|
|
|
|
|
+ System.out.println("getMessage() 输出:" + e.getMessage());
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 输出异常的完整堆栈信息(printStackTrace())
|
|
|
|
|
+ System.out.println("printStackTrace() 输出(请补充调用):");
|
|
|
|
|
+ // 请在下面调用 e.printStackTrace();
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 输出异常的类名
|
|
|
|
|
+ System.out.println("异常类名:" + e.getClass().getName());
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 输出异常的原因(getCause())
|
|
|
|
|
+ System.out.println("getCause() 输出:" + e.getCause());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 任务 2:体验 fillInStackTrace()
|
|
|
|
|
+ * 模拟多层方法调用中的异常传递
|
|
|
|
|
+ */
|
|
|
|
|
+ public static void methodA() throws Exception {
|
|
|
|
|
+ methodB(); // 调用 methodB,methodB 会抛出异常
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static void methodB() throws Exception {
|
|
|
|
|
+ // 请在此处手动抛出一个 Exception,描述信息为 "methodB 中发生异常"
|
|
|
|
|
+ // 使用 throw new Exception("...")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static void testFillInStackTrace() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ methodA();
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ System.out.println("原始异常信息:" + e.getMessage());
|
|
|
|
|
+ System.out.println("原始堆栈(调用 printStackTrace):");
|
|
|
|
|
+ e.printStackTrace(); // 显示原始异常发生位置
|
|
|
|
|
+
|
|
|
|
|
+ System.out.println("\n--- 以下为 fillInStackTrace 后的异常 ---");
|
|
|
|
|
+ // 调用 fillInStackTrace() 会重置堆栈信息到当前行
|
|
|
|
|
+ // 使得异常看起来像是在当前方法抛出的
|
|
|
|
|
+ Throwable newException = e.fillInStackTrace();
|
|
|
|
|
+ newException.printStackTrace();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 任务 3:模拟异常链(getCause)
|
|
|
|
|
+ * 将底层异常包装为高层异常,保留 cause 信息
|
|
|
|
|
+ */
|
|
|
|
|
+ public static void readFile(String path) throws Exception {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 模拟一个底层异常:文件未找到
|
|
|
|
|
+ throw new FileNotFoundException("文件 " + path + " 不存在!");
|
|
|
|
|
+ } catch (FileNotFoundException e) {
|
|
|
|
|
+ // 将底层异常包装为高层异常,并保留 cause
|
|
|
|
|
+ throw new Exception("读取文件失败,路径:" + path, e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static void testExceptionChain() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ readFile("/data/config.txt");
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ System.out.println("===== 异常链测试 =====");
|
|
|
|
|
+ System.out.println("高层异常信息:" + e.getMessage());
|
|
|
|
|
+ System.out.println("高层异常类名:" + e.getClass().getName());
|
|
|
|
|
+
|
|
|
|
|
+ // 获取底层异常(cause)
|
|
|
|
|
+ Throwable cause = e.getCause();
|
|
|
|
|
+ System.out.println("\n底层异常(cause):" + cause);
|
|
|
|
|
+ System.out.println("底层异常信息:" + cause.getMessage());
|
|
|
|
|
+ System.out.println("底层异常类名:" + cause.getClass().getName());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static void main(String[] args) {
|
|
|
|
|
+ System.out.println("========== 任务 1:getMessage 与 printStackTrace ==========");
|
|
|
|
|
+ int[] arr = {1, 2, 3};
|
|
|
|
|
+ accessArray(arr, 5);
|
|
|
|
|
+
|
|
|
|
|
+ System.out.println("\n========== 任务 2:fillInStackTrace ==========");
|
|
|
|
|
+ testFillInStackTrace();
|
|
|
|
|
+
|
|
|
|
|
+ System.out.println("\n========== 任务 3:异常链 getCause ==========");
|
|
|
|
|
+ testExceptionChain();
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 请回答以下问题
|
|
|
|
|
+
|
|
|
|
|
+1. 运行任务 1,观察控制台输出,说说 `getMessage()` 返回的内容和 `printStackTrace()` 输出的内容有什么不同?
|
|
|
|
|
+
|
|
|
|
|
+2. 任务 2 中,`fillInStackTrace()` 的作用是什么?调用前后 `printStackTrace` 输出的堆栈信息有什么变化?
|
|
|
|
|
+
|
|
|
|
|
+3. 任务 3 中,`e.getCause()` 获取到的是什么?如果不使用带 `cause` 参数的构造方法(`new Exception(msg, cause)`),`getCause()` 会返回什么?
|
|
|
|
|
+
|
|
|
|
|
+4. 请填写以下异常常用方法对比表:
|
|
|
|
|
+
|
|
|
|
|
+| 方法 | 返回值类型 | 作用 |
|
|
|
|
|
+|------|-----------|------|
|
|
|
|
|
+| `getMessage()` | ①______ | ②______ |
|
|
|
|
|
+| `printStackTrace()` | ③______ | ④______ |
|
|
|
|
|
+| `getCause()` | ⑤______ | ⑥______ |
|
|
|
|
|
+| `fillInStackTrace()` | ⑦______ | ⑧______ |
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 练习 8:throw 关键字——手动抛出异常
|
|
|
|
|
+
|
|
|
|
|
+**难度**:⭐⭐
|
|
|
|
|
+**知识点**:`throw` 关键字、手动创建异常对象、方法内抛出的业务逻辑判断
|
|
|
|
|
+
|
|
|
|
|
+**场景描述**:在开发中,当方法检测到某种业务条件不满足时,可以使用 `throw` 关键字手动抛出异常。本练习要求在各种业务场景中,自主判断何时需要手动抛出异常。
|
|
|
|
|
+
|
|
|
|
|
+**题目**:完成以下 `ThrowDemo` 类中的方法,使用 `throw` 手动抛出异常。
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+public class ThrowDemo {
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 任务 1:setAge() — 年龄校验
|
|
|
|
|
+ * 如果年龄 < 0 或 > 130,使用 throw 手动抛出 IllegalArgumentException
|
|
|
|
|
+ * 提示信息:"年龄必须在 0~130 之间!"
|
|
|
|
|
+ */
|
|
|
|
|
+ public static void setAge(int age) {
|
|
|
|
|
+ if (age < 0 || age > 130) {
|
|
|
|
|
+ // 请使用 throw 手动抛出 IllegalArgumentException
|
|
|
|
|
+ // 请补充代码
|
|
|
|
|
+ }
|
|
|
|
|
+ System.out.println("设置年龄成功:" + age);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 任务 2:withdraw() — 银行取款校验
|
|
|
|
|
+ * 如果取款金额 <= 0,抛出 IllegalArgumentException
|
|
|
|
|
+ * 提示信息:"取款金额必须大于 0!"
|
|
|
|
|
+ * 如果取款金额 > 余额,抛出 IllegalArgumentException
|
|
|
|
|
+ * 提示信息:"余额不足!当前余额:" + balance + " 元"
|
|
|
|
|
+ * 如果通过校验,扣除余额并输出:"取款成功,剩余余额:" + balance + " 元"
|
|
|
|
|
+ */
|
|
|
|
|
+ public static void withdraw(double balance, double amount) {
|
|
|
|
|
+ // 请补充代码:校验金额合法性,手动抛出异常
|
|
|
|
|
+
|
|
|
|
|
+ // 请补充代码:校验余额是否充足,手动抛出异常
|
|
|
|
|
+
|
|
|
|
|
+ // 通过校验,执行取款
|
|
|
|
|
+ balance -= amount;
|
|
|
|
|
+ System.out.println("取款成功,剩余余额:" + balance + " 元");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 任务 3:parseInt() — 字符串转整数(更友好的错误提示)
|
|
|
|
|
+ * 如果传入的字符串为 null,抛出 NullPointerException
|
|
|
|
|
+ * 提示信息:"输入的字符串不能为 null!"
|
|
|
|
|
+ * 如果传入的字符串为空或只有空格,抛出 IllegalArgumentException
|
|
|
|
|
+ * 提示信息:"输入的字符串不能为空!"
|
|
|
|
|
+ * 如果字符串包含非数字字符,抛出 NumberFormatException
|
|
|
|
|
+ * 提示信息:"字符串 \"" + str + "\" 包含非数字字符,无法转换!"
|
|
|
|
|
+ * 通过校验后,返回 Integer.parseInt(str)
|
|
|
|
|
+ */
|
|
|
|
|
+ public static int parseInt(String str) {
|
|
|
|
|
+ // 请补充代码:判断 null
|
|
|
|
|
+
|
|
|
|
|
+ // 请补充代码:判断空字符串
|
|
|
|
|
+
|
|
|
|
|
+ // 请补充代码:逐个字符检查是否都是数字(提示:str.toCharArray() 转字符数组遍历)
|
|
|
|
|
+ // 如果不是数字,使用 throw 手动抛出 NumberFormatException
|
|
|
|
|
+
|
|
|
|
|
+ // 通过校验,正常转换
|
|
|
|
|
+ return Integer.parseInt(str);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 任务 4:throw 后代码不会执行
|
|
|
|
|
+ * 验证一旦执行了 throw 语句,后面的代码将不会被执行
|
|
|
|
|
+ */
|
|
|
|
|
+ public static void testThrowExit() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ System.out.println("throw 之前的代码");
|
|
|
|
|
+ throw new RuntimeException("手动抛出的异常");
|
|
|
|
|
+ // System.out.println("throw 之后的代码"); // 编译错误:不可达语句
|
|
|
|
|
+ } catch (RuntimeException e) {
|
|
|
|
|
+ System.out.println("捕获到异常:" + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ System.out.println("catch 块之后的代码继续执行");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static void main(String[] args) {
|
|
|
|
|
+ System.out.println("========== 任务 1:年龄校验 ==========");
|
|
|
|
|
+ setAge(25); // 正常
|
|
|
|
|
+ setAge(-5); // 抛出异常
|
|
|
|
|
+
|
|
|
|
|
+ System.out.println("\n========== 任务 2:取款校验 ==========");
|
|
|
|
|
+ withdraw(1000, 500); // 正常
|
|
|
|
|
+ withdraw(1000, -100); // 金额不合法
|
|
|
|
|
+ withdraw(1000, 2000); // 余额不足
|
|
|
|
|
+
|
|
|
|
|
+ System.out.println("\n========== 任务 3:字符串转整数 ==========");
|
|
|
|
|
+ System.out.println(parseInt("123")); // 正常 → 123
|
|
|
|
|
+ // System.out.println(parseInt(null)); // 抛出 NullPointerException
|
|
|
|
|
+ // System.out.println(parseInt("")); // 抛出 IllegalArgumentException
|
|
|
|
|
+ // System.out.println(parseInt("12a3")); // 抛出 NumberFormatException
|
|
|
|
|
+
|
|
|
|
|
+ System.out.println("\n========== 任务 4:throw 后的代码不会执行 ==========");
|
|
|
|
|
+ testThrowExit();
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 思考题
|
|
|
|
|
+
|
|
|
|
|
+1. 在任务 1 中,为什么 `throw` 后面不需要 `try-catch`?`IllegalArgumentException` 是编译期异常还是运行时异常?
|
|
|
|
|
+2. 在任务 3 的 `parseInt()` 方法中,手动检查了字符串内容后调用了 `Integer.parseInt()`,这一步还有可能抛出异常吗?为什么?
|
|
|
|
|
+3. 仔细看任务 4:注释说 `"throw 之后的代码"` 会编译报错(不可达语句),这说明了 `throw` 的执行特性是什么?
|
|
|
|
|
+4. 对比 `throw` 和 `return`:两者都能让方法提前结束,有什么区别?
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
# 第二部分:进阶练习
|
|
# 第二部分:进阶练习
|
|
|
|
|
|
|
|
> 以下为综合挑战题,融合多种异常类型、异常处理的最佳实践、自定义异常等知识点。
|
|
> 以下为综合挑战题,融合多种异常类型、异常处理的最佳实践、自定义异常等知识点。
|
|
@@ -966,6 +1206,315 @@ public void withdraw(double amount)
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
|
|
+## 第 3 题:综合应用——商品库存管理系统(throw + throws + 自定义异常 + 编译期 vs 运行时异常对比)⭐⭐⭐⭐
|
|
|
|
|
+
|
|
|
|
|
+### 需求说明
|
|
|
|
|
+
|
|
|
|
|
+开发一个商品库存管理系统,核心要求:
|
|
|
|
|
+1. 定义两个自定义异常——继承 `Exception`(编译期)和继承 `RuntimeException`(运行时)
|
|
|
|
|
+2. 使用 `throw` 手动抛出异常
|
|
|
|
|
+3. 使用 `throws` 声明异常
|
|
|
|
|
+4. 体会编译期异常和运行时异常在强制处理上的区别
|
|
|
|
|
+
|
|
|
|
|
+### 需求分解
|
|
|
|
|
+
|
|
|
|
|
+#### 自定义异常类
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+// ===== 编译期异常(继承 Exception)=====
|
|
|
|
|
+// 商品不存在异常
|
|
|
|
|
+class ProductNotFoundException extends Exception {
|
|
|
|
|
+ public ProductNotFoundException(String message) {
|
|
|
|
|
+ super(message);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 库存不足异常
|
|
|
|
|
+class StockNotEnoughException extends Exception {
|
|
|
|
|
+ public StockNotEnoughException(String message) {
|
|
|
|
|
+ super(message);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ===== 运行时异常(继承 RuntimeException)=====
|
|
|
|
|
+// 商品价格异常(价格为负数或为 0)
|
|
|
|
|
+class InvalidPriceException extends RuntimeException {
|
|
|
|
|
+ public InvalidPriceException(String message) {
|
|
|
|
|
+ super(message);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 商品数量异常(数量为负数)
|
|
|
|
|
+class InvalidQuantityException extends RuntimeException {
|
|
|
|
|
+ public InvalidQuantityException(String message) {
|
|
|
|
|
+ super(message);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### Product 类
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+public class Product {
|
|
|
|
|
+ private String id; // 商品编号
|
|
|
|
|
+ private String name; // 商品名称
|
|
|
|
|
+ private double price; // 单价
|
|
|
|
|
+ private int stock; // 库存数量
|
|
|
|
|
+
|
|
|
|
|
+ // 构造方法
|
|
|
|
|
+ public Product(String id, String name, double price, int stock) {
|
|
|
|
|
+ this.id = id;
|
|
|
|
|
+ this.name = name;
|
|
|
|
|
+ // 调用 setPrice 和 setStock 进行校验
|
|
|
|
|
+ setPrice(price);
|
|
|
|
|
+ setStock(stock);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // getter 方法
|
|
|
|
|
+ public String getId() { return id; }
|
|
|
|
|
+ public String getName() { return name; }
|
|
|
|
|
+ public double getPrice() { return price; }
|
|
|
|
|
+ public int getStock() { return stock; }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 设置价格(运行时异常)
|
|
|
|
|
+ * 如果 price <= 0,抛出 InvalidPriceException
|
|
|
|
|
+ * 提示信息:"商品价格必须大于 0!当前价格:" + price
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setPrice(double price) {
|
|
|
|
|
+ // 请补充代码:使用 throw 抛出 InvalidPriceException(运行时异常)
|
|
|
|
|
+ // 注意:因为 InvalidPriceException 继承 RuntimeException,
|
|
|
|
|
+ // 所以方法上不需要 throws 声明
|
|
|
|
|
+ this.price = price;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 设置库存数量(运行时异常)
|
|
|
|
|
+ * 如果 stock < 0,抛出 InvalidQuantityException
|
|
|
|
|
+ * 提示信息:"商品库存数量不能为负数!当前数量:" + stock
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setStock(int stock) {
|
|
|
|
|
+ // 请补充代码:使用 throw 抛出 InvalidQuantityException(运行时异常)
|
|
|
|
|
+ this.stock = stock;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public String toString() {
|
|
|
|
|
+ return "Product{id='" + id + "', name='" + name + "', price=" + price + ", stock=" + stock + "}";
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### InventoryManager 类
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+public class InventoryManager {
|
|
|
|
|
+ private Product[] products; // 商品数组
|
|
|
|
|
+ private int count; // 实际商品数量
|
|
|
|
|
+
|
|
|
|
|
+ public InventoryManager(int capacity) {
|
|
|
|
|
+ this.products = new Product[capacity];
|
|
|
|
|
+ this.count = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 添加商品(运行时异常自行处理)
|
|
|
|
|
+ * 如果商品数组已满,抛出 RuntimeException
|
|
|
|
|
+ * 提示信息:"库存管理系统已满,无法添加新商品!"
|
|
|
|
|
+ */
|
|
|
|
|
+ public void addProduct(Product product) {
|
|
|
|
|
+ if (count >= products.length) {
|
|
|
|
|
+ // 请补充代码:使用 throw 抛出 RuntimeException(运行时异常)
|
|
|
|
|
+ }
|
|
|
|
|
+ products[count] = product;
|
|
|
|
|
+ count++;
|
|
|
|
|
+ System.out.println("商品已入库:" + product.getName());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 根据商品编号查找商品
|
|
|
|
|
+ * @param productId 商品编号
|
|
|
|
|
+ * @return 商品对象
|
|
|
|
|
+ * @throws ProductNotFoundException 商品不存在(编译期异常)
|
|
|
|
|
+ */
|
|
|
|
|
+ public Product findProduct(String productId) throws ProductNotFoundException {
|
|
|
|
|
+ for (int i = 0; i < count; i++) {
|
|
|
|
|
+ if (products[i].getId().equals(productId)) {
|
|
|
|
|
+ return products[i];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 请补充代码:使用 throw 抛出 ProductNotFoundException(编译期异常)
|
|
|
|
|
+ // 提示信息:"商品编号 " + productId + " 不存在!"
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 出库操作(减少库存)
|
|
|
|
|
+ * @param productId 商品编号
|
|
|
|
|
+ * @param quantity 出库数量
|
|
|
|
|
+ * @throws ProductNotFoundException 商品不存在
|
|
|
|
|
+ * @throws StockNotEnoughException 库存不足
|
|
|
|
|
+ */
|
|
|
|
|
+ public void outbound(String productId, int quantity)
|
|
|
|
|
+ throws ProductNotFoundException, StockNotEnoughException {
|
|
|
|
|
+ // 1. 调用 findProduct() 查找商品(会抛出 ProductNotFoundException)
|
|
|
|
|
+ Product product = findProduct(productId);
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 检查库存是否充足
|
|
|
|
|
+ // 如果 product.getStock() < quantity,抛出 StockNotEnoughException
|
|
|
|
|
+ // 提示信息:"商品 " + product.getName() + " 库存不足!当前库存:"
|
|
|
|
|
+ // + product.getStock() + ",需要:" + quantity
|
|
|
|
|
+ // 请补充代码
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 减少库存
|
|
|
|
|
+ product.setStock(product.getStock() - quantity);
|
|
|
|
|
+ System.out.println("出库成功:" + product.getName() + " × " + quantity
|
|
|
|
|
+ + ",剩余库存:" + product.getStock());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 入库操作(增加库存)—— 编译期异常在方法内部 try-catch 处理
|
|
|
|
|
+ * @param productId 商品编号
|
|
|
|
|
+ * @param quantity 入库数量
|
|
|
|
|
+ */
|
|
|
|
|
+ public void inbound(String productId, int quantity) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 调用 findProduct(),这里需要处理 ProductNotFoundException
|
|
|
|
|
+ Product product = findProduct(productId);
|
|
|
|
|
+ product.setStock(product.getStock() + quantity);
|
|
|
|
|
+ System.out.println("入库成功:" + product.getName() + " × " + quantity
|
|
|
|
|
+ + ",当前库存:" + product.getStock());
|
|
|
|
|
+ } catch (ProductNotFoundException e) {
|
|
|
|
|
+ System.out.println("入库失败:" + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 显示所有商品信息
|
|
|
|
|
+ */
|
|
|
|
|
+ public void showAllProducts() {
|
|
|
|
|
+ System.out.println("\n===== 当前库存列表 =====");
|
|
|
|
|
+ if (count == 0) {
|
|
|
|
|
+ System.out.println("仓库为空!");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ for (int i = 0; i < count; i++) {
|
|
|
|
|
+ System.out.println(products[i]);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 测试类要求
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+public class InventoryTest {
|
|
|
|
|
+ public static void main(String[] args) {
|
|
|
|
|
+ InventoryManager manager = new InventoryManager(5);
|
|
|
|
|
+
|
|
|
|
|
+ // ===== 1. 添加商品 =====
|
|
|
|
|
+ System.out.println("========== 1. 添加商品 ==========");
|
|
|
|
|
+ // 正常添加
|
|
|
|
|
+ manager.addProduct(new Product("P001", "华为手机", 5999.0, 100));
|
|
|
|
|
+ manager.addProduct(new Product("P002", "苹果笔记本", 12999.0, 50));
|
|
|
|
|
+ manager.addProduct(new Product("P003", "小米平板", 3499.0, 80));
|
|
|
|
|
+
|
|
|
|
|
+ // 以下两行会抛出运行时异常,应如何处理?
|
|
|
|
|
+ // 思考:因为是 RuntimeException,不强制 try-catch
|
|
|
|
|
+ // 尝试取消注释,观察运行效果
|
|
|
|
|
+ // manager.addProduct(new Product("P004", "测试手机", -100, 10)); // 价格异常
|
|
|
|
|
+ // manager.addProduct(new Product("P005", "测试平板", 2000, -5)); // 数量异常
|
|
|
|
|
+
|
|
|
|
|
+ manager.showAllProducts();
|
|
|
|
|
+
|
|
|
|
|
+ // ===== 2. 出库操作(编译期异常,必须 try-catch 或 throws)=====
|
|
|
|
|
+ System.out.println("\n========== 2. 出库操作 ==========");
|
|
|
|
|
+
|
|
|
|
|
+ // 正常出库
|
|
|
|
|
+ try {
|
|
|
|
|
+ manager.outbound("P001", 10);
|
|
|
|
|
+ } catch (ProductNotFoundException e) {
|
|
|
|
|
+ System.out.println("出库失败:" + e.getMessage());
|
|
|
|
|
+ } catch (StockNotEnoughException e) {
|
|
|
|
|
+ System.out.println("出库失败:" + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 库存不足
|
|
|
|
|
+ try {
|
|
|
|
|
+ manager.outbound("P003", 200); // 库存只有 80
|
|
|
|
|
+ } catch (ProductNotFoundException | StockNotEnoughException e) {
|
|
|
|
|
+ System.out.println("出库失败:" + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 商品不存在
|
|
|
|
|
+ try {
|
|
|
|
|
+ manager.outbound("P999", 10);
|
|
|
|
|
+ } catch (ProductNotFoundException | StockNotEnoughException e) {
|
|
|
|
|
+ System.out.println("出库失败:" + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ===== 3. 入库操作(编译期异常已在 inbound 内部处理)=====
|
|
|
|
|
+ System.out.println("\n========== 3. 入库操作 ==========");
|
|
|
|
|
+ manager.inbound("P002", 20); // 正常入库
|
|
|
|
|
+ manager.inbound("P999", 10); // 商品不存在,内部已捕获
|
|
|
|
|
+
|
|
|
|
|
+ // ===== 4. 显示最终库存 =====
|
|
|
|
|
+ manager.showAllProducts();
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 预期输出
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+========== 1. 添加商品 ==========
|
|
|
|
|
+商品已入库:华为手机
|
|
|
|
|
+商品已入库:苹果笔记本
|
|
|
|
|
+商品已入库:小米平板
|
|
|
|
|
+
|
|
|
|
|
+===== 当前库存列表 =====
|
|
|
|
|
+Product{id='P001', name='华为手机', price=5999.0, stock=100}
|
|
|
|
|
+Product{id='P002', name='苹果笔记本', price=12999.0, stock=50}
|
|
|
|
|
+Product{id='P003', name='小米平板', price=3499.0, stock=80}
|
|
|
|
|
+
|
|
|
|
|
+========== 2. 出库操作 ==========
|
|
|
|
|
+出库成功:华为手机 × 10,剩余库存:90
|
|
|
|
|
+出库失败:商品 小米平板 库存不足!当前库存:80,需要:200
|
|
|
|
|
+出库失败:商品编号 P999 不存在!
|
|
|
|
|
+
|
|
|
|
|
+========== 3. 入库操作 ==========
|
|
|
|
|
+入库成功:苹果笔记本 × 20,当前库存:70
|
|
|
|
|
+入库失败:商品编号 P999 不存在!
|
|
|
|
|
+
|
|
|
|
|
+===== 当前库存列表 =====
|
|
|
|
|
+Product{id='P001', name='华为手机', price=5999.0, stock=90}
|
|
|
|
|
+Product{id='P002', name='苹果笔记本', price=12999.0, stock=70}
|
|
|
|
|
+Product{id='P003', name='小米平板', price=3499.0, stock=80}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 对比分析填空
|
|
|
|
|
+
|
|
|
|
|
+请根据本练习的代码,完成下面的对比表格:
|
|
|
|
|
+
|
|
|
|
|
+| 对比维度 | 编译期异常(继承 Exception) | 运行时异常(继承 RuntimeException) |
|
|
|
|
|
+|---------|---------------------------|----------------------------------|
|
|
|
|
|
+| 异常类示例 | ①______、②______ | ③______、④______ |
|
|
|
|
|
+| 是否需要 throws 声明 | ⑤______ | ⑥______ |
|
|
|
|
|
+| 调用者是否必须处理(try-catch) | ⑦______ | ⑧______ |
|
|
|
|
|
+| 典型场景 | ⑨______ | ⑩______ |
|
|
|
|
|
+| 方法 `setPrice()` 抛出的异常类型 | ⑪______ | —— |
|
|
|
|
|
+| 方法 `findProduct()` 抛出的异常类型 | —— | ⑫______ |
|
|
|
|
|
+
|
|
|
|
|
+### 思考题
|
|
|
|
|
+
|
|
|
|
|
+1. 在本练习中,`setPrice()` 抛出的是运行时异常,所以方法上不需要 `throws InvalidPriceException` 声明。如果将 `InvalidPriceException` 改为继承 `Exception`,`setPrice()` 方法需要做什么修改?哪些调用 `setPrice()` 的地方需要修改?
|
|
|
|
|
+2. `outbound()` 方法声明了 `throws ProductNotFoundException, StockNotEnoughException`,在 `InventoryTest` 的 main 方法中调用 `outbound()` 时,编译期是否会报错?main 方法是否也必须用 `throws` 抛出?
|
|
|
|
|
+3. 对比 `InventoryTest` 中调用 `outbound()`(需要 try-catch)和调用 `addProduct()`(不需要 try-catch),哪种异常更容易使用?哪种异常更容易被开发者忽略?
|
|
|
|
|
+4. 在实际项目开发中,什么情况下应该设计为编译期异常(Checked Exception),什么情况下应该设计为运行时异常(Unchecked Exception)?请结合本练习中的例子说明。
|
|
|
|
|
+5. 请阅读 `inbound()` 方法的代码:它在方法内部用 try-catch 处理了 `ProductNotFoundException`。这样做和 `outbound()` 方法把异常抛给调用者处理,各有什么优缺点?
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
## 📋 题目总览
|
|
## 📋 题目总览
|
|
|
|
|
|
|
|
| 序号 | 所属部分 | 核心知识点 | 难度 |
|
|
| 序号 | 所属部分 | 核心知识点 | 难度 |
|
|
@@ -976,5 +1525,8 @@ public void withdraw(double amount)
|
|
|
| 练习 4 | 随堂 | ArithmeticException、多 catch 块顺序(子类→父类) | ⭐⭐ |
|
|
| 练习 4 | 随堂 | ArithmeticException、多 catch 块顺序(子类→父类) | ⭐⭐ |
|
|
|
| 练习 5 | 随堂 | 异常处理与程序鲁棒性、多异常场景处理、try-catch 综合运用 | ⭐⭐⭐ |
|
|
| 练习 5 | 随堂 | 异常处理与程序鲁棒性、多异常场景处理、try-catch 综合运用 | ⭐⭐⭐ |
|
|
|
| 练习 6 | 随堂 | 多 catch 块精确匹配、异常类层次结构、throws 抛出 | ⭐⭐ |
|
|
| 练习 6 | 随堂 | 多 catch 块精确匹配、异常类层次结构、throws 抛出 | ⭐⭐ |
|
|
|
|
|
+| 练习 7 | 随堂 | 异常常用方法(getMessage / printStackTrace / getCause / fillInStackTrace) | ⭐⭐ |
|
|
|
|
|
+| 练习 8 | 随堂 | throw 关键字手动抛出异常、业务逻辑校验、throw 执行特性 | ⭐⭐ |
|
|
|
| 进阶 1 | 进阶 | 自定义异常(继承 Exception)、业务校验、多 catch 块业务处理 | ⭐⭐⭐⭐ |
|
|
| 进阶 1 | 进阶 | 自定义异常(继承 Exception)、业务校验、多 catch 块业务处理 | ⭐⭐⭐⭐ |
|
|
|
| 进阶 2 | 进阶 | 自定义异常 + 业务逻辑 + 封装 + 异常处理最佳实践(银行 ATM) | ⭐⭐⭐⭐⭐ |
|
|
| 进阶 2 | 进阶 | 自定义异常 + 业务逻辑 + 封装 + 异常处理最佳实践(银行 ATM) | ⭐⭐⭐⭐⭐ |
|
|
|
|
|
+| 进阶 3 | 进阶 | 编译期异常 vs 运行时异常(继承 Exception vs RuntimeException)、throw + throws 完整实战、商品库存管理 | ⭐⭐⭐⭐ |
|