|
@@ -0,0 +1,980 @@
|
|
|
|
|
+# 2026-07-28 异常处理(Exception Handling)基础练习(课堂练习用)
|
|
|
|
|
+
|
|
|
|
|
+> **说明**:以下习题围绕今日授课核心内容——**Java 异常处理(Exception Handling)** 展开,涵盖 Throwable 体系结构、try-catch 多 catch 块捕获处理、常见运行时异常(数组下标越界、空指针、类型转换、算术异常等)、异常处理的鲁棒性等知识点。
|
|
|
|
|
+>
|
|
|
|
|
+> 题目分为两部分:**随堂练习**(异常处理基础,6 题)和 **进阶练习**(综合挑战,2 题)。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+# 第一部分:随堂练习
|
|
|
|
|
+
|
|
|
|
|
+> 以下练习围绕异常处理的基础概念和用法,由浅入深。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 练习 1:数组下标越界异常捕获(ArrayIndexOutOfBoundsException)
|
|
|
|
|
+
|
|
|
|
|
+**难度**:⭐
|
|
|
|
|
+**知识点**:try-catch 基本结构、数组下标越界异常捕获、多 catch 块、程序健壮性
|
|
|
|
|
+
|
|
|
|
|
+**场景描述**:程序运行时经常会遇到数组下标越界的情况,如果不做处理,JVM 会终止程序运行。本练习要求使用 try-catch 捕获数组下标越界异常,让程序能够继续执行后续代码。
|
|
|
|
|
+
|
|
|
|
|
+**题目**:完成以下 `Demo01` 类,创建一个长度为 3 的 int 数组,尝试访问索引 5 的元素,并用 try-catch 捕获异常。
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+public class Demo01 {
|
|
|
|
|
+ public static void main(String[] args) {
|
|
|
|
|
+ int[] arr = {10, 20, 30}; // 长度 = 3,有效索引:0, 1, 2
|
|
|
|
|
+
|
|
|
|
|
+ // 请使用 try-catch 捕获数组下标越界异常
|
|
|
|
|
+ // 要求:
|
|
|
|
|
+ // 1. 在 try 块中尝试访问 arr[5](会抛出 ArrayIndexOutOfBoundsException)
|
|
|
|
|
+ // 2. 使用 catch 捕获 ArrayIndexOutOfBoundsException
|
|
|
|
|
+ // 3. 输出:"数组下标越界!索引 5 超出了数组长度 " + arr.length
|
|
|
|
|
+ // 4. 无论是否发生异常,最后都要输出:"程序继续运行..."
|
|
|
|
|
+
|
|
|
|
|
+ // 请在此补充代码
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**预期输出**:
|
|
|
|
|
+```
|
|
|
|
|
+数组下标越界!索引 5 超出了数组长度 3
|
|
|
|
|
+程序继续运行...
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**思考题**:
|
|
|
|
|
+1. 如果 catch 块中写的是 `catch (Exception e)`,能否捕获到 `ArrayIndexOutOfBoundsException`?为什么?
|
|
|
|
|
+2. 如果 try 块中先执行 `arr[5]`,再执行 `arr[1]`,`arr[1]` 还能正常执行吗?为什么?
|
|
|
|
|
+3. try-catch 结构中的代码执行顺序是怎样的?(正常情况和异常情况分别说明)
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 练习 2:空指针异常捕获(NullPointerException)
|
|
|
|
|
+
|
|
|
|
|
+**难度**:⭐
|
|
|
|
|
+**知识点**:空指针异常、引用类型默认值 null、数组元素为 null 的访问
|
|
|
|
|
+
|
|
|
|
|
+**场景描述**:程序中经常会出现对 `null` 值调用方法或访问属性的情况,会触发 `NullPointerException`。本练习要求捕获空指针异常,使程序不会因一个空指针而完全崩溃。
|
|
|
|
|
+
|
|
|
|
|
+**题目**:完成以下 `Demo02` 类,处理对象数组中的 null 元素。
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+public class Demo02 {
|
|
|
|
|
+ public static void main(String[] args) {
|
|
|
|
|
+ String[] names = new String[5]; // 所有元素初始为 null
|
|
|
|
|
+ names[0] = "张三";
|
|
|
|
|
+ names[2] = "李四";
|
|
|
|
|
+ names[4] = "王五";
|
|
|
|
|
+
|
|
|
|
|
+ // 要求:遍历 names 数组,对每个元素调用 .length() 方法获取字符串长度
|
|
|
|
|
+ // 如果元素为 null,会抛出 NullPointerException,请用 try-catch 捕获
|
|
|
|
|
+ // 输出格式:"元素 i 的长度为:xx"(正常)
|
|
|
|
|
+ // 输出格式:"元素 i 为 null,跳过"(异常时)
|
|
|
|
|
+ // 要求使用 try-catch 包裹在循环内部,让循环可以继续执行
|
|
|
|
|
+
|
|
|
|
|
+ for (int i = 0; i < names.length; i++) {
|
|
|
|
|
+ // 请在此补充 try-catch 代码
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ System.out.println("遍历完成!");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**预期输出**:
|
|
|
|
|
+```
|
|
|
|
|
+元素 0 的长度为:2
|
|
|
|
|
+元素 1 为 null,跳过
|
|
|
|
|
+元素 2 的长度为:2
|
|
|
|
|
+元素 3 为 null,跳过
|
|
|
|
|
+元素 4 的长度为:2
|
|
|
|
|
+遍历完成!
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**思考题**:
|
|
|
|
|
+1. 如果把 try-catch 放在 for 循环**外面**,运行结果会有什么不同?为什么?
|
|
|
|
|
+2. `NullPointerException` 是 `RuntimeException` 的子类吗?它属于编译期异常还是运行时异常?
|
|
|
|
|
+3. 除了用 try-catch,还有什么方式可以避免空指针异常?
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 练习 3:类型转换异常捕获(ClassCastException)
|
|
|
|
|
+
|
|
|
|
|
+**难度**:⭐⭐
|
|
|
|
|
+**知识点**:多态中的向下转型、ClassCastException、instanceof 运算符、类型安全的向下转型
|
|
|
|
|
+
|
|
|
|
|
+**场景描述**:在多态编程中,向下转型(将父类引用强转为子类类型)时,如果对象的实际类型与目标类型不匹配,会抛出 `ClassCastException`。本练习通过 try-catch 捕获此类异常,同时练习使用 `instanceof` 来预防。
|
|
|
|
|
+
|
|
|
|
|
+**题目**:定义动物类体系,完成以下 `Demo03` 类。
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+// 已有类定义(无需修改)
|
|
|
|
|
+class Animal {
|
|
|
|
|
+ public void speak() {
|
|
|
|
|
+ System.out.println("动物发出叫声");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+class Dog extends Animal {
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void speak() {
|
|
|
|
|
+ System.out.println("汪汪汪!");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void guard() {
|
|
|
|
|
+ System.out.println("狗狗在看家...");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+class Cat extends Animal {
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void speak() {
|
|
|
|
|
+ System.out.println("喵喵喵!");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void scratch() {
|
|
|
|
|
+ System.out.println("猫咪在抓老鼠...");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**要求**:完成 `Demo03` 的 `main` 方法。
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+public class Demo03 {
|
|
|
|
|
+ public static void main(String[] args) {
|
|
|
|
|
+ Animal[] animals = new Animal[3];
|
|
|
|
|
+ animals[0] = new Dog();
|
|
|
|
|
+ animals[1] = new Cat();
|
|
|
|
|
+ animals[2] = new Dog();
|
|
|
|
|
+
|
|
|
|
|
+ // 任务 1:使用 try-catch 遍历数组,将每个元素向下转型为 Dog
|
|
|
|
|
+ // 如果是 Dog 类型,调用 guard() 方法
|
|
|
|
|
+ // 如果是 Cat 类型,转型会抛出 ClassCastException,捕获并输出:"第 i 个元素不是 Dog,跳过"
|
|
|
|
|
+ // 请补充代码
|
|
|
|
|
+ System.out.println("===== 任务 1:强制向下转型(可能抛异常)=====");
|
|
|
|
|
+ for (int i = 0; i < animals.length; i++) {
|
|
|
|
|
+ // 请在此补充 try-catch 代码
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 任务 2:使用 instanceof 安全地进行向下转型(不依赖异常)
|
|
|
|
|
+ System.out.println("\n===== 任务 2:使用 instanceof 安全转型 =====");
|
|
|
|
|
+ for (int i = 0; i < animals.length; i++) {
|
|
|
|
|
+ // 请使用 instanceof 判断后再转型,调用 guard() 或 scratch()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**预期输出**:
|
|
|
|
|
+```
|
|
|
|
|
+===== 任务 1:强制向下转型(可能抛异常)=====
|
|
|
|
|
+狗狗在看家...
|
|
|
|
|
+第 1 个元素不是 Dog,跳过
|
|
|
|
|
+狗狗在看家...
|
|
|
|
|
+
|
|
|
|
|
+===== 任务 2:使用 instanceof 安全转型 =====
|
|
|
|
|
+狗狗在看家...
|
|
|
|
|
+猫咪在抓老鼠...
|
|
|
|
|
+狗狗在看家...
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**思考题**:
|
|
|
|
|
+1. 任务 1 和任务 2 两种方式各有什么优缺点?哪种方式更推荐?
|
|
|
|
|
+2. `instanceof` 运算符的作用是什么?它能不能判断一个对象是否是某个接口的实现?
|
|
|
|
|
+3. 使用异常来控制程序流程(如任务 1)有什么问题?(提示:异常处理的性能开销)
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 练习 4:算术异常与多 catch 块组合(ArithmeticException)
|
|
|
|
|
+
|
|
|
|
|
+**难度**:⭐⭐
|
|
|
|
|
+**知识点**:ArithmeticException(除零异常)、多 catch 块的使用、catch 顺序(子类 → 父类)
|
|
|
|
|
+
|
|
|
|
|
+**场景描述**:在数学计算中,除零操作会引发 `ArithmeticException`。本练习要求使用多个 catch 块处理不同类型的异常,体会 catch 块的匹配顺序。
|
|
|
|
|
+
|
|
|
|
|
+**题目**:完成以下 `Demo04` 类,模拟一个简易计算器的除法功能,处理多种输入异常。
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+import java.util.Scanner;
|
|
|
|
|
+
|
|
|
|
|
+public class Demo04 {
|
|
|
|
|
+ public static void main(String[] args) {
|
|
|
|
|
+ Scanner scanner = new Scanner(System.in);
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ System.out.print("请输入被除数:");
|
|
|
|
|
+ String input1 = scanner.nextLine();
|
|
|
|
|
+ System.out.print("请输入除数:");
|
|
|
|
|
+ String input2 = scanner.nextLine();
|
|
|
|
|
+
|
|
|
|
|
+ int a = Integer.parseInt(input1); // 可能抛出 NumberFormatException
|
|
|
|
|
+ int b = Integer.parseInt(input2); // 可能抛出 NumberFormatException
|
|
|
|
|
+
|
|
|
|
|
+ int result = a / b; // 可能抛出 ArithmeticException
|
|
|
|
|
+ System.out.println("计算结果:" + a + " ÷ " + b + " = " + result);
|
|
|
|
|
+
|
|
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
|
|
+ System.out.println("输入格式错误:请输入整数数字!");
|
|
|
|
|
+ } catch (ArithmeticException e) {
|
|
|
|
|
+ System.out.println("除数不能为零!");
|
|
|
|
|
+ } catch (RuntimeException e) {
|
|
|
|
|
+ System.out.println("发生了运行时异常:" + e.getMessage());
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ System.out.println("发生了其他异常:" + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ System.out.println("程序结束,感谢使用计算器!");
|
|
|
|
|
+
|
|
|
|
|
+ scanner.close();
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**测试运行要求**:
|
|
|
|
|
+请根据以下输入情况,写出对应的输出结果:
|
|
|
|
|
+
|
|
|
|
|
+| 输入(被除数, 除数) | 预期输出 |
|
|
|
|
|
+|---------------------|---------|
|
|
|
|
|
+| 10, 2 | ①______ |
|
|
|
|
|
+| 10, 0 | ②______ |
|
|
|
|
|
+| abc, 5 | ③______ |
|
|
|
|
|
+| 10, null(直接回车) | ④______(提示:思考 `Integer.parseInt("")` 是否会抛异常) |
|
|
|
|
|
+
|
|
|
|
|
+**思考题**:
|
|
|
|
|
+1. 本练习中 catch 块的顺序是 `NumberFormatException` → `ArithmeticException` → `RuntimeException` → `Exception`,如果调换顺序,比如把 `Exception` 放在最前面,会怎么样?(编译能通过吗?实际运行有什么问题?)
|
|
|
|
|
+2. `NumberFormatException` 和 `ArithmeticException` 分别属于哪个异常类的子类?
|
|
|
|
|
+3. 如果 try 块中第一行就抛出了异常,try 块中后面的代码还会执行吗?
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 练习 5:异常处理的鲁棒性——学生成绩处理系统
|
|
|
|
|
+
|
|
|
|
|
+**难度**:⭐⭐⭐
|
|
|
|
|
+**知识点**:try-catch-finally 综合运用、异常处理提升程序健壮性、多异常场景处理
|
|
|
|
|
+
|
|
|
|
|
+**场景描述**:开发一个学生成绩处理系统,接收输入的学生成绩数组,计算总分和平均分。系统需要处理各种异常情况(空数组、null 元素、非法成绩值等),保证在任何异常情况下程序都能继续运行。
|
|
|
|
|
+
|
|
|
|
|
+**题目**:完成以下 `ScoreManager` 类。
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+public class ScoreManager {
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 计算成绩数组的总分
|
|
|
|
|
+ * @param scores 成绩数组
|
|
|
|
|
+ * @return 总分,如果数组为 null 或长度为 0,返回 0
|
|
|
|
|
+ */
|
|
|
|
|
+ public static double sum(Double[] scores) {
|
|
|
|
|
+ // 要求:
|
|
|
|
|
+ // 1. 如果 scores 为 null,输出 "成绩数组为 null!",返回 0
|
|
|
|
|
+ // 2. 如果 scores 长度为 0,输出 "成绩数组为空!",返回 0
|
|
|
|
|
+ // 3. 遍历数组累加,如果某个元素为 null,输出 "第 i 个成绩为 null,跳过"
|
|
|
|
|
+ // 但继续累加后续元素
|
|
|
|
|
+ // 4. 返回总分
|
|
|
|
|
+
|
|
|
|
|
+ // 请补充代码
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 计算成绩数组的平均分
|
|
|
|
|
+ * @param scores 成绩数组
|
|
|
|
|
+ * @return 平均分,如果发生异常返回 0
|
|
|
|
|
+ */
|
|
|
|
|
+ public static double average(Double[] scores) {
|
|
|
|
|
+ // 要求:
|
|
|
|
|
+ // 1. 调用 sum() 方法获取总分
|
|
|
|
|
+ // 2. 计算有效成绩的个数(非 null 的元素个数)
|
|
|
|
|
+ // 3. 如果有效个数为 0,输出 "没有有效成绩,无法计算平均分",返回 0
|
|
|
|
|
+ // 4. 返回总分 / 有效个数
|
|
|
|
|
+ // 5. 使用 try-catch 包裹整个方法逻辑,捕获任意异常,输出 "计算平均分时发生异常"
|
|
|
|
|
+ // 并返回 0
|
|
|
|
|
+
|
|
|
|
|
+ // 请补充代码
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static void main(String[] args) {
|
|
|
|
|
+ // ===== 测试案例 1:正常数据 =====
|
|
|
|
|
+ Double[] scores1 = {85.5, 92.0, 78.5, 88.0, 95.5};
|
|
|
|
|
+ System.out.println("===== 学生成绩 1 =====");
|
|
|
|
|
+ System.out.println("总分:" + sum(scores1));
|
|
|
|
|
+ System.out.println("平均分:" + average(scores1));
|
|
|
|
|
+ System.out.println();
|
|
|
|
|
+
|
|
|
|
|
+ // ===== 测试案例 2:包含 null 元素 =====
|
|
|
|
|
+ Double[] scores2 = {85.5, null, 78.5, null, 95.5};
|
|
|
|
|
+ System.out.println("===== 学生成绩 2(含 null)=====");
|
|
|
|
|
+ System.out.println("总分:" + sum(scores2));
|
|
|
|
|
+ System.out.println("平均分:" + average(scores2));
|
|
|
|
|
+ System.out.println();
|
|
|
|
|
+
|
|
|
|
|
+ // ===== 测试案例 3:空数组 =====
|
|
|
|
|
+ Double[] scores3 = {};
|
|
|
|
|
+ System.out.println("===== 学生成绩 3(空数组)=====");
|
|
|
|
|
+ System.out.println("总分:" + sum(scores3));
|
|
|
|
|
+ System.out.println("平均分:" + average(scores3));
|
|
|
|
|
+ System.out.println();
|
|
|
|
|
+
|
|
|
|
|
+ // ===== 测试案例 4:null 数组 =====
|
|
|
|
|
+ Double[] scores4 = null;
|
|
|
|
|
+ System.out.println("===== 学生成绩 4(null 数组)=====");
|
|
|
|
|
+ System.out.println("总分:" + sum(scores4));
|
|
|
|
|
+ System.out.println("平均分:" + average(scores4));
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**预期输出**:
|
|
|
|
|
+```
|
|
|
|
|
+===== 学生成绩 1 =====
|
|
|
|
|
+总分:439.5
|
|
|
|
|
+平均分:87.9
|
|
|
|
|
+
|
|
|
|
|
+===== 学生成绩 2(含 null)=====
|
|
|
|
|
+第 1 个成绩为 null,跳过
|
|
|
|
|
+第 3 个成绩为 null,跳过
|
|
|
|
|
+总分:259.5
|
|
|
|
|
+平均分:86.5
|
|
|
|
|
+
|
|
|
|
|
+===== 学生成绩 3(空数组)=====
|
|
|
|
|
+成绩数组为空!
|
|
|
|
|
+总分:0.0
|
|
|
|
|
+没有有效成绩,无法计算平均分
|
|
|
|
|
+平均分:0.0
|
|
|
|
|
+
|
|
|
|
|
+===== 学生成绩 4(null 数组)=====
|
|
|
|
|
+成绩数组为 null!
|
|
|
|
|
+总分:0.0
|
|
|
|
|
+计算平均分时发生异常
|
|
|
|
|
+平均分:0.0
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**思考题**:
|
|
|
|
|
+1. 为什么在 `average()` 方法中使用了 try-catch 包裹整个方法?如果不包裹,`scores = null` 的情况下会发生什么?
|
|
|
|
|
+2. `sum()` 方法中先判断 null 再判断空数组,这个顺序能调换吗?为什么?
|
|
|
|
|
+3. 这个学生成绩处理系统体现了什么编程思想?如果没有异常处理,用户在使用时会有怎样的体验?
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 练习 6:多 catch 块 + 异常向上抛出
|
|
|
|
|
+
|
|
|
|
|
+**难度**:⭐⭐
|
|
|
|
|
+**知识点**:多 catch 块精确匹配、异常类层次结构、catch 顺序规则、throws 抛出异常
|
|
|
|
|
+
|
|
|
|
|
+**场景描述**:理解多 catch 块的匹配规则,以及何时使用 throws 将异常抛给调用者处理。
|
|
|
|
|
+
|
|
|
|
|
+**题目**:阅读以下代码,回答问题。
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+public class Demo06 {
|
|
|
|
|
+
|
|
|
|
|
+ public static void methodA(int i) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (i == 0) {
|
|
|
|
|
+ int[] arr = {};
|
|
|
|
|
+ System.out.println(arr[0]); // 情况 A
|
|
|
|
|
+ } else if (i == 1) {
|
|
|
|
|
+ Object obj = null;
|
|
|
|
|
+ obj.toString(); // 情况 B
|
|
|
|
|
+ } else if (i == 2) {
|
|
|
|
|
+ Object obj = new Integer(10);
|
|
|
|
|
+ String str = (String) obj; // 情况 C
|
|
|
|
|
+ } else if (i == 3) {
|
|
|
|
|
+ int a = 10 / 0; // 情况 D
|
|
|
|
|
+ } else if (i == 4) {
|
|
|
|
|
+ int[] arr = new int[-1]; // 情况 E
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (ArrayIndexOutOfBoundsException e) {
|
|
|
|
|
+ System.out.println("捕获:数组下标越界");
|
|
|
|
|
+ } catch (NullPointerException e) {
|
|
|
|
|
+ System.out.println("捕获:空指针");
|
|
|
|
|
+ } catch (ClassCastException e) {
|
|
|
|
|
+ System.out.println("捕获:类型转换");
|
|
|
|
|
+ } catch (ArithmeticException e) {
|
|
|
|
|
+ System.out.println("捕获:算术异常");
|
|
|
|
|
+ } catch (RuntimeException e) {
|
|
|
|
|
+ System.out.println("捕获:运行时异常:" + e.getClass().getSimpleName());
|
|
|
|
|
+ }
|
|
|
|
|
+ System.out.println("方法执行完毕");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static void main(String[] args) {
|
|
|
|
|
+ // 请写出每种情况的输出结果
|
|
|
|
|
+ // methodA(0); // 输出:①______
|
|
|
|
|
+ // methodA(1); // 输出:②______
|
|
|
|
|
+ // methodA(2); // 输出:③______
|
|
|
|
|
+ // methodA(3); // 输出:④______
|
|
|
|
|
+ // methodA(4); // 输出:⑤______
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**补充题**:请完成以下 `methodB` 方法,体会 throws 关键字的使用。
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+// 请将以下方法补充完整:
|
|
|
|
|
+// 该方法接收两个字符串参数,将它们转换为整数并相除
|
|
|
|
|
+// 方法内部不处理异常,将异常全部抛给调用者
|
|
|
|
|
+public static int divide(String s1, String s2) /* 请补充 throws 声明 */ {
|
|
|
|
|
+ // 请补充代码:将 s1 和 s2 转换为 int,返回 s1 / s2
|
|
|
|
|
+ // 不处理任何异常,直接抛出
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**问题**:
|
|
|
|
|
+1. 在 `methodA` 中,如果将最后一个 catch 块 `catch (RuntimeException e)` 移动到最前面,会有什么后果?
|
|
|
|
|
+2. `NegativeArraySizeException` 是什么异常?它继承自哪个类?
|
|
|
|
|
+3. `throws` 关键字和 `try-catch` 各有什么适用场景?什么情况下应该使用 `throws` 而不是 `try-catch`?
|
|
|
|
|
+4. 如果 `methodB` 声明了 `throws NumberFormatException, ArithmeticException`,调用者调用 `methodB` 时是否必须使用 try-catch?
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+# 第二部分:进阶练习
|
|
|
|
|
+
|
|
|
|
|
+> 以下为综合挑战题,融合多种异常类型、异常处理的最佳实践、自定义异常等知识点。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 第 1 题:综合应用——用户注册验证系统(异常处理综合)⭐⭐⭐⭐
|
|
|
|
|
+
|
|
|
|
|
+### 需求说明
|
|
|
|
|
+
|
|
|
|
|
+开发一个用户注册验证系统,对用户输入的注册信息进行校验。当输入不合法时,使用 **自定义异常** 和 **多 catch 处理** 给出明确提示。
|
|
|
|
|
+
|
|
|
|
|
+### 自定义异常类
|
|
|
|
|
+
|
|
|
|
|
+定义以下自定义异常类(继承 `Exception` —— 编译期异常):
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+// 用户名不能为空异常
|
|
|
|
|
+class UsernameEmptyException extends Exception {
|
|
|
|
|
+ public UsernameEmptyException(String message) {
|
|
|
|
|
+ super(message);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 用户名长度异常(长度必须在 3~12 个字符之间)
|
|
|
|
|
+class UsernameLengthException extends Exception {
|
|
|
|
|
+ public UsernameLengthException(String message) {
|
|
|
|
|
+ super(message);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 密码不能为空异常
|
|
|
|
|
+class PasswordEmptyException extends Exception {
|
|
|
|
|
+ public PasswordEmptyException(String message) {
|
|
|
|
|
+ super(message);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 密码强度异常(必须包含数字和字母,长度 >= 6)
|
|
|
|
|
+class PasswordWeakException extends Exception {
|
|
|
|
|
+ public PasswordWeakException(String message) {
|
|
|
|
|
+ super(message);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 年龄异常(年龄必须在 0~150 之间)
|
|
|
|
|
+class AgeInvalidException extends Exception {
|
|
|
|
|
+ public AgeInvalidException(String message) {
|
|
|
|
|
+ super(message);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### RegisterService 类
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+public class RegisterService {
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 验证用户名
|
|
|
|
|
+ * @param username 用户名
|
|
|
|
|
+ * @throws UsernameEmptyException 用户名为空
|
|
|
|
|
+ * @throws UsernameLengthException 用户名长度不在 3~12 之间
|
|
|
|
|
+ */
|
|
|
|
|
+ public static void validateUsername(String username)
|
|
|
|
|
+ throws UsernameEmptyException, UsernameLengthException {
|
|
|
|
|
+ // 1. 如果 username 为 null 或 trim() 后长度为 0,抛出 UsernameEmptyException
|
|
|
|
|
+ // 提示信息:"用户名不能为空!"
|
|
|
|
|
+ // 2. 如果 username.length() < 3 或 > 12,抛出 UsernameLengthException
|
|
|
|
|
+ // 提示信息:"用户名长度必须在 3~12 个字符之间!"
|
|
|
|
|
+ // 请补充代码
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 验证密码
|
|
|
|
|
+ * @param password 密码
|
|
|
|
|
+ * @throws PasswordEmptyException 密码为空
|
|
|
|
|
+ * @throws PasswordWeakException 密码强度不足
|
|
|
|
|
+ */
|
|
|
|
|
+ public static void validatePassword(String password)
|
|
|
|
|
+ throws PasswordEmptyException, PasswordWeakException {
|
|
|
|
|
+ // 1. 如果 password 为 null 或 trim() 后长度为 0,抛出 PasswordEmptyException
|
|
|
|
|
+ // 提示信息:"密码不能为空!"
|
|
|
|
|
+ // 2. 如果 password.length() < 6,抛出 PasswordWeakException
|
|
|
|
|
+ // 提示信息:"密码长度不能少于 6 位!"
|
|
|
|
|
+ // 3. 如果 password 不包含数字或字母(判断技巧:使用正则或循环遍历逐个字符检查),
|
|
|
|
|
+ // 抛出 PasswordWeakException
|
|
|
|
|
+ // 提示信息:"密码必须包含数字和字母!"
|
|
|
|
|
+ // 请补充代码(建议:使用循环遍历 + 两个 boolean 标记)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 验证年龄
|
|
|
|
|
+ * @param age 年龄字符串
|
|
|
|
|
+ * @throws NumberFormatException 年龄不是数字
|
|
|
|
|
+ * @throws AgeInvalidException 年龄超出范围
|
|
|
|
|
+ */
|
|
|
|
|
+ public static void validateAge(String age)
|
|
|
|
|
+ throws NumberFormatException, AgeInvalidException {
|
|
|
|
|
+ // 1. 使用 Integer.parseInt(age) 转换为 int
|
|
|
|
|
+ // 如果 age 不是有效数字,NumberFormatException 会自动抛出(无需手动处理)
|
|
|
|
|
+ // 2. 如果 age < 0 或 age > 150,抛出 AgeInvalidException
|
|
|
|
|
+ // 提示信息:"年龄必须在 0~150 之间!"
|
|
|
|
|
+ // 请补充代码
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 注册用户
|
|
|
|
|
+ * @param username 用户名
|
|
|
|
|
+ * @param password 密码
|
|
|
|
|
+ * @param age 年龄
|
|
|
|
|
+ */
|
|
|
|
|
+ public static void register(String username, String password, String age) {
|
|
|
|
|
+ // 要求:
|
|
|
|
|
+ // 1. 使用 try-catch 调用各验证方法
|
|
|
|
|
+ // 2. 按顺序验证:用户名 → 密码 → 年龄
|
|
|
|
|
+ // 3. 每个验证都有独立的 catch 块,输出对应的错误提示
|
|
|
|
|
+ // 4. 如果所有验证都通过,输出:"注册成功!欢迎 " + username
|
|
|
|
|
+ // 5. 注意:validateAge() 可能抛出 NumberFormatException,也需要捕获
|
|
|
|
|
+
|
|
|
|
|
+ // 请补充代码
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static void main(String[] args) {
|
|
|
|
|
+ // 测试用例
|
|
|
|
|
+ System.out.println("========== 测试 1:正常注册 ==========");
|
|
|
|
|
+ register("张三丰", "abc123", "25");
|
|
|
|
|
+
|
|
|
|
|
+ System.out.println("\n========== 测试 2:用户名为空 ==========");
|
|
|
|
|
+ register("", "abc123", "25");
|
|
|
|
|
+
|
|
|
|
|
+ System.out.println("\n========== 测试 3:用户名太短 ==========");
|
|
|
|
|
+ register("12", "abc123", "25");
|
|
|
|
|
+
|
|
|
|
|
+ System.out.println("\n========== 测试 4:密码太短 ==========");
|
|
|
|
|
+ register("张三丰", "12", "25");
|
|
|
|
|
+
|
|
|
|
|
+ System.out.println("\n========== 测试 5:密码不包含数字 ==========");
|
|
|
|
|
+ register("张三丰", "abcdef", "25");
|
|
|
|
|
+
|
|
|
|
|
+ System.out.println("\n========== 测试 6:年龄不是数字 ==========");
|
|
|
|
|
+ register("张三丰", "abc123", "二十五");
|
|
|
|
|
+
|
|
|
|
|
+ System.out.println("\n========== 测试 7:年龄超出范围 ==========");
|
|
|
|
|
+ register("张三丰", "abc123", "200");
|
|
|
|
|
+
|
|
|
|
|
+ System.out.println("\n========== 测试 8:综合错误(多错误按顺序只报第一个)==========");
|
|
|
|
|
+ register("", "", "abc"); // 应该只报用户名错误,不会检查密码和年龄
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 预期输出
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+========== 测试 1:正常注册 ==========
|
|
|
|
|
+注册成功!欢迎 张三丰
|
|
|
|
|
+
|
|
|
|
|
+========== 测试 2:用户名为空 ==========
|
|
|
|
|
+用户名不能为空!
|
|
|
|
|
+
|
|
|
|
|
+========== 测试 3:用户名太短 ==========
|
|
|
|
|
+用户名长度必须在 3~12 个字符之间!
|
|
|
|
|
+
|
|
|
|
|
+========== 测试 4:密码太短 ==========
|
|
|
|
|
+密码长度不能少于 6 位!
|
|
|
|
|
+
|
|
|
|
|
+========== 测试 5:密码不包含数字 ==========
|
|
|
|
|
+密码必须包含数字和字母!
|
|
|
|
|
+
|
|
|
|
|
+========== 测试 6:年龄不是数字 ==========
|
|
|
|
|
+年龄格式错误:二十五 不是有效的数字!
|
|
|
|
|
+
|
|
|
|
|
+========== 测试 7:年龄超出范围 ==========
|
|
|
|
|
+年龄必须在 0~150 之间!
|
|
|
|
|
+
|
|
|
|
|
+========== 测试 8:综合错误 ==========
|
|
|
|
|
+用户名不能为空!
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 思考题
|
|
|
|
|
+
|
|
|
|
|
+1. 本练习中,自定义异常都继承了 `Exception`(编译期异常),这有什么影响?如果改为继承 `RuntimeException`,代码会有什么变化?
|
|
|
|
|
+2. 为什么 `register()` 方法中,每个验证方法都有自己的 catch 块,而不是用一个大 `catch (Exception e)` 统一处理?这样做的好处是什么?
|
|
|
|
|
+3. 测试 8 中,用户名、密码、年龄同时出错,为什么只报了用户名的错误?这体现了什么设计思想?
|
|
|
|
|
+4. 自定义异常在实际项目中有哪些应用场景?什么时候应该自定义异常而不是使用 Java 内置异常?
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 第 2 题:综合应用——银行 ATM 取款系统(异常处理 + 封装 + 业务逻辑)⭐⭐⭐⭐⭐
|
|
|
|
|
+
|
|
|
|
|
+### 需求说明
|
|
|
|
|
+
|
|
|
|
|
+模拟银行 ATM 取款系统。该系统包含账户类、ATM 机类和自定义业务异常类。要求使用异常处理来处理各种业务情况(余额不足、取款金额超限、账户不存在等),体现 **异常处理在业务逻辑中的价值**。
|
|
|
|
|
+
|
|
|
|
|
+### 类设计
|
|
|
|
|
+
|
|
|
|
|
+#### 自定义异常类(继承 Exception —— 编译期异常)
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+// 账户不存在异常
|
|
|
|
|
+class AccountNotFoundException extends Exception {
|
|
|
|
|
+ public AccountNotFoundException(String message) {
|
|
|
|
|
+ super(message);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 余额不足异常
|
|
|
|
|
+class InsufficientBalanceException extends Exception {
|
|
|
|
|
+ public InsufficientBalanceException(String message) {
|
|
|
|
|
+ super(message);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 取款金额超限异常(单次取款不能超过 5000 元)
|
|
|
|
|
+class WithdrawLimitExceededException extends Exception {
|
|
|
|
|
+ public WithdrawLimitExceededException(String message) {
|
|
|
|
|
+ super(message);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 无效金额异常(取款金额必须是正数,且为 100 的倍数)
|
|
|
|
|
+class InvalidAmountException extends Exception {
|
|
|
|
|
+ public InvalidAmountException(String message) {
|
|
|
|
|
+ super(message);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### Account 类
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+public class Account {
|
|
|
|
|
+ private String accountId; // 账号
|
|
|
|
|
+ private String holderName; // 户主姓名
|
|
|
|
|
+ private double balance; // 余额
|
|
|
|
|
+
|
|
|
|
|
+ // 构造方法
|
|
|
|
|
+ public Account(String accountId, String holderName, double initialBalance) {
|
|
|
|
|
+ this.accountId = accountId;
|
|
|
|
|
+ this.holderName = holderName;
|
|
|
|
|
+ this.balance = initialBalance;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // getter 方法
|
|
|
|
|
+ public String getAccountId() { return accountId; }
|
|
|
|
|
+ public String getHolderName() { return holderName; }
|
|
|
|
|
+ public double getBalance() { return balance; }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 存款
|
|
|
|
|
+ * @param amount 存款金额
|
|
|
|
|
+ */
|
|
|
|
|
+ public void deposit(double amount) {
|
|
|
|
|
+ if (amount <= 0) {
|
|
|
|
|
+ System.out.println("存款金额必须大于 0!");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ balance += amount;
|
|
|
|
|
+ System.out.println(holderName + " 存入 " + amount + " 元,余额:" + balance + " 元");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 取款
|
|
|
|
|
+ * @param amount 取款金额
|
|
|
|
|
+ * @throws InvalidAmountException 金额不合法
|
|
|
|
|
+ * @throws WithdrawLimitExceededException 超过单次取款限制
|
|
|
|
|
+ * @throws InsufficientBalanceException 余额不足
|
|
|
|
|
+ */
|
|
|
|
|
+ public void withdraw(double amount)
|
|
|
|
|
+ throws InvalidAmountException, WithdrawLimitExceededException, InsufficientBalanceException {
|
|
|
|
|
+ // 1. 如果 amount <= 0 或者 amount % 100 != 0,抛出 InvalidAmountException
|
|
|
|
|
+ // 提示信息:"取款金额必须为正数且为 100 的倍数!"
|
|
|
|
|
+ // 2. 如果 amount > 5000,抛出 WithdrawLimitExceededException
|
|
|
|
|
+ // 提示信息:"单次取款金额不能超过 5000 元!"
|
|
|
|
|
+ // 3. 如果 amount > balance,抛出 InsufficientBalanceException
|
|
|
|
|
+ // 提示信息:"余额不足!当前余额:" + balance + " 元,需要:" + amount + " 元"
|
|
|
|
|
+ // 4. 通过所有检查后,扣除余额,输出:
|
|
|
|
|
+ // holderName + " 取出 " + amount + " 元,剩余余额:" + balance + " 元"
|
|
|
|
|
+ // 请补充代码
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 查询余额
|
|
|
|
|
+ */
|
|
|
|
|
+ public void showBalance() {
|
|
|
|
|
+ System.out.println("===== " + holderName + " 的账户 =====");
|
|
|
|
|
+ System.out.println("账号:" + accountId);
|
|
|
|
|
+ System.out.println("余额:" + balance + " 元");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### ATM 类
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+public class ATM {
|
|
|
|
|
+ private String location; // ATM 位置
|
|
|
|
|
+ private Account[] accounts; // 管理的账户数组
|
|
|
|
|
+ private int accountCount; // 实际账户数量
|
|
|
|
|
+
|
|
|
|
|
+ public ATM(String location, int maxAccounts) {
|
|
|
|
|
+ this.location = location;
|
|
|
|
|
+ this.accounts = new Account[maxAccounts];
|
|
|
|
|
+ this.accountCount = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 添加账户到 ATM 系统
|
|
|
|
|
+ */
|
|
|
|
|
+ public void addAccount(Account account) {
|
|
|
|
|
+ if (accountCount < accounts.length) {
|
|
|
|
|
+ accounts[accountCount] = account;
|
|
|
|
|
+ accountCount++;
|
|
|
|
|
+ System.out.println("账户 " + account.getAccountId() + " 已添加到 " + location + " ATM 系统");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ System.out.println("ATM 账户已满,无法添加新账户!");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 根据账号查找账户
|
|
|
|
|
+ * @param accountId 账号
|
|
|
|
|
+ * @return 账户对象
|
|
|
|
|
+ * @throws AccountNotFoundException 账户不存在
|
|
|
|
|
+ */
|
|
|
|
|
+ private Account findAccount(String accountId) throws AccountNotFoundException {
|
|
|
|
|
+ // 遍历 accounts 数组查找匹配的账号
|
|
|
|
|
+ // 如果找到,返回该 Account 对象
|
|
|
|
|
+ // 如果没找到,抛出 AccountNotFoundException
|
|
|
|
|
+ // 提示信息:"账号 " + accountId + " 不存在!"
|
|
|
|
|
+ // 请补充代码
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * ATM 取款
|
|
|
|
|
+ * @param accountId 账号
|
|
|
|
|
+ * @param amount 取款金额
|
|
|
|
|
+ */
|
|
|
|
|
+ public void withdraw(String accountId, double amount) {
|
|
|
|
|
+ System.out.println("\n【" + location + " ATM】取款操作:账号 " + accountId + ",金额 " + amount + " 元");
|
|
|
|
|
+
|
|
|
|
|
+ // 要求:
|
|
|
|
|
+ // 1. 使用 try-catch 调用 findAccount() 查找账户
|
|
|
|
|
+ // 2. 找到账户后,调用 account.withdraw(amount) 执行取款
|
|
|
|
|
+ // 3. 捕获以下异常并给出提示:
|
|
|
|
|
+ // - AccountNotFoundException:输出 "取款失败:账号不存在!"
|
|
|
|
|
+ // - InvalidAmountException:输出 "取款失败:金额不合法!" + e.getMessage()
|
|
|
|
|
+ // - WithdrawLimitExceededException:输出 "取款失败:超过限额!" + e.getMessage()
|
|
|
|
|
+ // - InsufficientBalanceException:输出 "取款失败:余额不足!" + e.getMessage()
|
|
|
|
|
+ // 4. 无论是否发生异常,最后输出:"感谢使用 " + location + " ATM 服务!"
|
|
|
|
|
+ // 请补充代码
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * ATM 存款
|
|
|
|
|
+ * @param accountId 账号
|
|
|
|
|
+ * @param amount 存款金额
|
|
|
|
|
+ */
|
|
|
|
|
+ public void deposit(String accountId, double amount) {
|
|
|
|
|
+ System.out.println("\n【" + location + " ATM】存款操作:账号 " + accountId + ",金额 " + amount + " 元");
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ Account account = findAccount(accountId);
|
|
|
|
|
+ account.deposit(amount);
|
|
|
|
|
+ } catch (AccountNotFoundException e) {
|
|
|
|
|
+ System.out.println("存款失败:" + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ System.out.println("感谢使用 " + location + " ATM 服务!");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 查询余额
|
|
|
|
|
+ */
|
|
|
|
|
+ public void checkBalance(String accountId) {
|
|
|
|
|
+ System.out.println("\n【" + location + " ATM】余额查询:账号 " + accountId);
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ Account account = findAccount(accountId);
|
|
|
|
|
+ account.showBalance();
|
|
|
|
|
+ } catch (AccountNotFoundException e) {
|
|
|
|
|
+ System.out.println("查询失败:" + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 测试类要求
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+public class ATMTest {
|
|
|
|
|
+ public static void main(String[] args) {
|
|
|
|
|
+ // 1. 创建 ATM 对象(位置:"幸福路支行",最大账户数:10)
|
|
|
|
|
+ ATM atm = new ATM("幸福路支行", 10);
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 创建几个账户
|
|
|
|
|
+ Account acc1 = new Account("A001", "张三", 10000);
|
|
|
|
|
+ Account acc2 = new Account("A002", "李四", 5000);
|
|
|
|
|
+ Account acc3 = new Account("A003", "王五", 2000);
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 将账户添加到 ATM
|
|
|
|
|
+ atm.addAccount(acc1);
|
|
|
|
|
+ atm.addAccount(acc2);
|
|
|
|
|
+ atm.addAccount(acc3);
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 测试正常取款
|
|
|
|
|
+ System.out.println("\n========== 正常取款 ==========");
|
|
|
|
|
+ atm.withdraw("A001", 2000); // 成功取出 2000
|
|
|
|
|
+
|
|
|
|
|
+ // 5. 测试余额不足
|
|
|
|
|
+ System.out.println("\n========== 余额不足 ==========");
|
|
|
|
|
+ atm.withdraw("A003", 3000); // 王五只有 2000,不足
|
|
|
|
|
+
|
|
|
|
|
+ // 6. 测试超过单次取款限额
|
|
|
|
|
+ System.out.println("\n========== 超过限额 ==========");
|
|
|
|
|
+ atm.withdraw("A001", 6000); // 超过 5000 限额
|
|
|
|
|
+
|
|
|
|
|
+ // 7. 测试金额不合法
|
|
|
|
|
+ System.out.println("\n========== 金额不合法 ==========");
|
|
|
|
|
+ atm.withdraw("A002", 123); // 不是 100 的倍数
|
|
|
|
|
+
|
|
|
|
|
+ // 8. 测试账户不存在
|
|
|
|
|
+ System.out.println("\n========== 账户不存在 ==========");
|
|
|
|
|
+ atm.withdraw("A999", 1000); // 不存在的账户
|
|
|
|
|
+
|
|
|
|
|
+ // 9. 测试存款
|
|
|
|
|
+ System.out.println("\n========== 存款 ==========");
|
|
|
|
|
+ atm.deposit("A002", 3000); // 存入 3000
|
|
|
|
|
+
|
|
|
|
|
+ // 10. 测试查询余额
|
|
|
|
|
+ System.out.println("\n========== 查询余额 ==========");
|
|
|
|
|
+ atm.checkBalance("A001");
|
|
|
|
|
+ atm.checkBalance("A002");
|
|
|
|
|
+ atm.checkBalance("A003");
|
|
|
|
|
+
|
|
|
|
|
+ // 11. 测试查询不存在的账户
|
|
|
|
|
+ System.out.println("\n========== 查询不存在的账户 ==========");
|
|
|
|
|
+ atm.checkBalance("A999");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 预期输出(参考)
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+账户 A001 已添加到 幸福路支行 ATM 系统
|
|
|
|
|
+账户 A002 已添加到 幸福路支行 ATM 系统
|
|
|
|
|
+账户 A003 已添加到 幸福路支行 ATM 系统
|
|
|
|
|
+
|
|
|
|
|
+========== 正常取款 ==========
|
|
|
|
|
+
|
|
|
|
|
+【幸福路支行 ATM】取款操作:账号 A001,金额 2000.0 元
|
|
|
|
|
+张三 取出 2000.0 元,剩余余额:8000.0 元
|
|
|
|
|
+感谢使用 幸福路支行 ATM 服务!
|
|
|
|
|
+
|
|
|
|
|
+========== 余额不足 ==========
|
|
|
|
|
+
|
|
|
|
|
+【幸福路支行 ATM】取款操作:账号 A003,金额 3000.0 元
|
|
|
|
|
+取款失败:余额不足!当前余额:2000.0 元,需要:3000.0 元
|
|
|
|
|
+感谢使用 幸福路支行 ATM 服务!
|
|
|
|
|
+
|
|
|
|
|
+========== 超过限额 ==========
|
|
|
|
|
+
|
|
|
|
|
+【幸福路支行 ATM】取款操作:账号 A001,金额 6000.0 元
|
|
|
|
|
+取款失败:超过限额!单次取款金额不能超过 5000 元!
|
|
|
|
|
+感谢使用 幸福路支行 ATM 服务!
|
|
|
|
|
+
|
|
|
|
|
+========== 金额不合法 ==========
|
|
|
|
|
+
|
|
|
|
|
+【幸福路支行 ATM】取款操作:账号 A002,金额 123.0 元
|
|
|
|
|
+取款失败:金额不合法!取款金额必须为正数且为 100 的倍数!
|
|
|
|
|
+感谢使用 幸福路支行 ATM 服务!
|
|
|
|
|
+
|
|
|
|
|
+========== 账户不存在 ==========
|
|
|
|
|
+
|
|
|
|
|
+【幸福路支行 ATM】取款操作:账号 A999,金额 1000.0 元
|
|
|
|
|
+取款失败:账号不存在!
|
|
|
|
|
+感谢使用 幸福路支行 ATM 服务!
|
|
|
|
|
+
|
|
|
|
|
+========== 存款 ==========
|
|
|
|
|
+
|
|
|
|
|
+【幸福路支行 ATM】存款操作:账号 A002,金额 3000.0 元
|
|
|
|
|
+李四 存入 3000.0 元,余额:8000.0 元
|
|
|
|
|
+感谢使用 幸福路支行 ATM 服务!
|
|
|
|
|
+
|
|
|
|
|
+========== 查询余额 ==========
|
|
|
|
|
+
|
|
|
|
|
+【幸福路支行 ATM】余额查询:账号 A001
|
|
|
|
|
+===== 张三 的账户 =====
|
|
|
|
|
+账号:A001
|
|
|
|
|
+余额:8000.0 元
|
|
|
|
|
+
|
|
|
|
|
+【幸福路支行 ATM】余额查询:账号 A002
|
|
|
|
|
+===== 李四 的账户 =====
|
|
|
|
|
+账号:A002
|
|
|
|
|
+余额:8000.0 元
|
|
|
|
|
+
|
|
|
|
|
+【幸福路支行 ATM】余额查询:账号 A003
|
|
|
|
|
+===== 王五 的账户 =====
|
|
|
|
|
+账号:A003
|
|
|
|
|
+余额:2000.0 元
|
|
|
|
|
+
|
|
|
|
|
+========== 查询不存在的账户 ==========
|
|
|
|
|
+
|
|
|
|
|
+【幸福路支行 ATM】余额查询:账号 A999
|
|
|
|
|
+查询失败:账号 A999 不存在!
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 代码补充挑战
|
|
|
|
|
+
|
|
|
|
|
+请补全 `Account.withdraw()` 方法中的异常抛出逻辑(参考前面给出的方法签名和注释):
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+public void withdraw(double amount)
|
|
|
|
|
+ throws InvalidAmountException, WithdrawLimitExceededException, InsufficientBalanceException {
|
|
|
|
|
+ // 请补充完整代码
|
|
|
|
|
+
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 思考题
|
|
|
|
|
+
|
|
|
|
|
+1. 本练习中,`Account.withdraw()` 将异常抛给了调用者(`ATM.withdraw()`),而不是在自己内部处理。这种设计有什么好处?
|
|
|
|
|
+2. `ATM.withdraw()` 使用了多个 catch 块分别处理不同的业务异常,这和用一个 `catch (Exception e)` 统一处理相比,哪种方式更好?为什么?
|
|
|
|
|
+3. 本练习中的业务异常(如 `InsufficientBalanceException`)继承了 `Exception`(编译期异常),如果改为继承 `RuntimeException`,`ATM.withdraw()` 中的 catch 能不能省略?省略后会有什么问题?
|
|
|
|
|
+4. 在实际的银行系统中,除了本练习提到的异常,还可能有哪些业务异常?请列举至少 3 个。
|
|
|
|
|
+5. `findAccount()` 方法为什么设计为 `private`?为什么抛出 `AccountNotFoundException` 而不是返回 `null`?用异常和返回 null 各有什么优缺点?
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 📋 题目总览
|
|
|
|
|
+
|
|
|
|
|
+| 序号 | 所属部分 | 核心知识点 | 难度 |
|
|
|
|
|
+|------|---------|-----------|------|
|
|
|
|
|
+| 练习 1 | 随堂 | ArrayIndexOutOfBoundsException 捕获、try-catch 基本结构 | ⭐ |
|
|
|
|
|
+| 练习 2 | 随堂 | NullPointerException 捕获、异常在循环内外的不同影响 | ⭐ |
|
|
|
|
|
+| 练习 3 | 随堂 | ClassCastException 捕获、instanceof 安全转型对比 | ⭐⭐ |
|
|
|
|
|
+| 练习 4 | 随堂 | ArithmeticException、多 catch 块顺序(子类→父类) | ⭐⭐ |
|
|
|
|
|
+| 练习 5 | 随堂 | 异常处理与程序鲁棒性、多异常场景处理、try-catch 综合运用 | ⭐⭐⭐ |
|
|
|
|
|
+| 练习 6 | 随堂 | 多 catch 块精确匹配、异常类层次结构、throws 抛出 | ⭐⭐ |
|
|
|
|
|
+| 进阶 1 | 进阶 | 自定义异常(继承 Exception)、业务校验、多 catch 块业务处理 | ⭐⭐⭐⭐ |
|
|
|
|
|
+| 进阶 2 | 进阶 | 自定义异常 + 业务逻辑 + 封装 + 异常处理最佳实践(银行 ATM) | ⭐⭐⭐⭐⭐ |
|