# 2026-07-23 抽象类(Abstract)基础练习(课堂练习用) > **说明**:以下习题围绕今日授课核心内容——**抽象类(abstract)** 展开,涵盖抽象类的定义、特点、抽象方法的重写以及抽象类与对象数组/排序的综合应用。 > > 题目分为三部分:**随堂练习**(抽象类入门,4 题)、**进阶练习**(综合挑战,2 题)和 **课外拓展**(1 题)。 --- # 第一部分:随堂练习 > 以下练习围绕抽象类的基础概念和用法,由浅入深。 --- ## 练习 1:抽象类入门——Instrument 乐器体系 **难度**:⭐ **知识点**:抽象类的定义、抽象方法、子类重写抽象方法 创建一个乐器抽象体系: **`Instrument` 抽象类**(父类): - **抽象方法**:`play()` —— 演奏乐器(无返回值、无参数) **`Piano` 类**(继承 Instrument): - **重写** `play()` —— 输出 `"弹钢琴:叮咚叮咚~"` **`Violin` 类**(继承 Instrument): - **重写** `play()` —— 输出 `"拉小提琴:咿呀咿呀~"` **`Drum` 类**(继承 Instrument): - **重写** `play()` —— 输出 `"打鼓:咚咚咚!"` **测试类要求**: 1. 创建 `Piano`、`Violin`、`Drum` 各一个对象 2. 分别调用它们的 `play()` 方法 3. 观察:相同的方法名 `play()` 在不同的子类中有不同的行为——这就是 **抽象方法多态** 的体现 **预期输出**: ``` 弹钢琴:叮咚叮咚~ 拉小提琴:咿呀咿呀~ 打鼓:咚咚咚! ``` --- ## 练习 2:形状抽象类——Shape 图形体系 ⭐(必做) **难度**:⭐⭐ **知识点**:抽象类定义、抽象方法(计算周长 + 计算面积)、子类重写所有抽象方法 创建一个图形抽象体系,要求抽象父类中定义 **计算周长** 和 **计算面积** 的抽象方法。 **`Shape` 抽象类**(父类): - **私有属性**:`color`(颜色,String) - **有参构造方法**:`Shape(String color)` - **抽象方法**:`double getArea()` —— 计算面积 - **抽象方法**:`double getPerimeter()` —— 计算周长 - **普通方法**:`showInfo()` —— 输出 `"颜色:xxx,面积:xxx,周长:xxx"`(通过调用 `getArea()` 和 `getPerimeter()` 获得) **`Circle` 类**(继承 Shape): - **新增私有属性**:`radius`(半径,double) - **构造方法**:`Circle(String color, double radius)` - **重写** `getArea()` —— 返回 `π × r²`(π 取 `3.1415926`) - **重写** `getPerimeter()` —— 返回 `2 × π × r` **`Rectangle` 类**(继承 Shape): - **新增私有属性**:`width`(宽度,double)、`height`(高度,double) - **构造方法**:`Rectangle(String color, double width, double height)` - **重写** `getArea()` —— 返回 `width × height` - **重写** `getPerimeter()` —— 返回 `2 × (width + height)` **`Triangle` 类**(继承 Shape): - **新增私有属性**:`a`(边长 a,double)、`b`(边长 b,double)、`c`(边长 c,double) - **构造方法**:`Triangle(String color, double a, double b, double c)` - **重写** `getArea()` —— 使用海伦公式:`s = (a+b+c)/2`,面积 = `√(s(s-a)(s-b)(s-c))`(`Math.sqrt()` 求平方根) - **重写** `getPerimeter()` —— 返回 `a + b + c` **测试类要求**: 1. 创建 `Circle` 对象("红色", 5.0),调用 `showInfo()` 输出信息 2. 创建 `Rectangle` 对象("蓝色", 4.0, 6.0),调用 `showInfo()` 输出信息 3. 创建 `Triangle` 对象("绿色", 3.0, 4.0, 5.0),调用 `showInfo()` 输出信息 **预期输出**(保留两位小数): ``` 颜色:红色,面积:78.54,周长:31.42 颜色:蓝色,面积:24.00,周长:20.00 颜色:绿色,面积:6.00,周长:12.00 ``` > **提示**: > - 子类构造方法中必须调用 `super(color)` 初始化父类的 color 属性 > - `Triangle` 的面积使用海伦公式:`double s = (a + b + c) / 2; double area = Math.sqrt(s * (s - a) * (s - b) * (s - c));` > - 输出时使用 `System.out.printf("%.2f", value)` 保留两位小数 --- ## 练习 3:员工抽象类——Employee 工作体系 **难度**:⭐⭐ **知识点**:抽象类、抽象方法重写、构造方法 + super() 创建一个员工工作体系: **`Employee` 抽象类**(父类): - **私有属性**:`name`(姓名,String)、`baseSalary`(基本工资,double) - **有参构造方法**:`Employee(String name, double baseSalary)` - **抽象方法**:`void work()` —— 工作内容 - **抽象方法**:`double calculateSalary()` —— 计算实际薪资 - **普通方法**:`showInfo()` —— 输出 `"姓名:xxx,薪资:xxx"` **`Manager` 类**(继承 Employee): - **新增私有属性**:`bonus`(岗位津贴,double) - **构造方法**:`Manager(String name, double baseSalary, double bonus)` - **重写** `work()` —— 输出 `"经理在管理团队"` - **重写** `calculateSalary()` —— 返回 `baseSalary + bonus` **`Programmer` 类**(继承 Employee): - **新增私有属性**:`projectBonus`(项目奖金,double) - **构造方法**:`Programmer(String name, double baseSalary, double projectBonus)` - **重写** `work()` —— 输出 `"程序员在编写代码"` - **重写** `calculateSalary()` —— 返回 `baseSalary + projectBonus` **测试类要求**: 1. 创建 `Manager` 对象("张经理", 15000, 5000),调用 `work()` 和 `showInfo()` 2. 创建 `Programmer` 对象("李编程", 12000, 3000),调用 `work()` 和 `showInfo()` 3. 思考:如果将 `Employee` 改为非抽象类,把 `work()` 和 `calculateSalary()` 改为普通方法,会有什么问题? **预期输出**: ``` 经理在管理团队 姓名:张经理,薪资:20000.0 程序员在编写代码 姓名:李编程,薪资:15000.0 ``` --- ## 练习 4:判断抽象类设计——哪些类应该定义为抽象类 **难度**:⭐⭐ **知识点**:抽象类设计思想、何时使用抽象类 判断以下场景中,哪些类 **应该** 定义为抽象类,哪些 **不应该**,并说明理由。 | 序号 | 类名 | 描述 | 是否抽象? | 理由 | |------|------|------|-----------|------| | 1 | `Animal` | 有 `move()` 方法,所有子类移动方式不同 | | | | 2 | `Car` | 有 `start()`、`stop()` 方法,所有汽车启动和停止方式相同 | | | | 3 | `Payment` | 有 `pay()` 方法,微信支付/支付宝/信用卡支付逻辑不同 | | | | 4 | `Student` | 有 `study()` 方法,小学生和大学生学习方式不同,但也有共性属性(学号、姓名) | | | | 5 | `MathUtils` | 只有静态工具方法 `max()`、`min()`、`abs()`,不需要子类 | | | | 6 | `Database` | 有 `connect()`、`disconnect()`、`executeQuery()`,MySQL/Oracle 实现不同 | | | **参考答案思路**: | 序号 | 是否抽象? | 理由 | |------|-----------|------| | 1 | ✅ 应该 | 不同动物的移动方式完全不同(走/飞/游),父类无法给出通用实现 | | 2 | ❌ 不应 | 所有汽车的启动和停止方式基本一致,用普通父类即可 | | 3 | ✅ 应该 | 不同支付方式的 `pay()` 逻辑完全不同 | | 4 | ✅ 应该 | 不同阶段学生学习方式不同,但`study()` 作为统一接口定义是合理的 | | 5 | ❌ 不应 | 工具类不需要继承体系,不应定义为抽象类 | | 6 | ✅ 应该 | 不同数据库的连接和查询方式不同,适合用抽象类定义规范 | --- # 第二部分:进阶练习 > 以下为综合挑战题,融合抽象类与对象数组、排序、多态等知识点。 --- ## 第 1 题:抽象类 + 对象数组 + 排序——Shape 图形面积排序 ⭐⭐⭐⭐ 综合运用抽象类、对象数组、冒泡排序、多态等知识点。 ### 需求说明 利用 **练习 2** 中定义的 `Shape` 抽象类及其子类(`Circle`、`Rectangle`、`Triangle`),创建一个图形数组,按 **面积从大到小** 排序。 ### 类设计 直接复用练习 2 中的 `Shape`(抽象类)、`Circle`、`Rectangle`、`Triangle` 类。 ### 测试类要求 **`ShapeSortTest` 类**: 1. **创建图形数组**:创建一个 `Shape[]` 数组,包含至少 5 个不同的图形对象 **示例数据**: ```java Shape s1 = new Circle("红色", 5.0); // 面积 ≈ 78.54 Shape s2 = new Rectangle("蓝色", 4.0, 6.0); // 面积 = 24.0 Shape s3 = new Triangle("绿色", 3.0, 4.0, 5.0); // 面积 = 6.0 Shape s4 = new Circle("黄色", 3.0); // 面积 ≈ 28.27 Shape s5 = new Rectangle("紫色", 8.0, 2.0); // 面积 = 16.0 ``` 2. **遍历输出所有图形信息**:调用每个元素的 `showInfo()` 方法 3. **按面积从大到小排序**:使用冒泡排序,按 `getArea()` 返回值降序排列 4. **排序后再次输出所有图形信息** 5. **查找面积最大的图形**:遍历数组,输出面积最大的图形信息 ### 思考题 1. 数组类型声明为 `Shape[]`,为什么能存储 `Circle`、`Rectangle`、`Triangle` 等不同类型的对象?这体现了 OOP 的哪个特征? 2. 如果新增一个 `Square`(正方形)类,它应该继承谁?需要重写哪些方法? 3. 抽象类 `Shape` 能否直接 `new Shape("黑色")` 创建对象?为什么? ### 预期输出(参考) ``` ========== 排序前 ========== 颜色:红色,面积:78.54,周长:31.42 颜色:蓝色,面积:24.00,周长:20.00 颜色:绿色,面积:6.00,周长:12.00 颜色:黄色,面积:28.27,周长:18.85 颜色:紫色,面积:16.00,周长:20.00 ========== 排序后(按面积降序) ========== 颜色:红色,面积:78.54,周长:31.42 颜色:黄色,面积:28.27,周长:18.85 颜色:蓝色,面积:24.00,周长:20.00 颜色:紫色,面积:16.00,周长:20.00 颜色:绿色,面积:6.00,周长:12.00 ========== 面积最大的图形 ========== 颜色:红色,面积:78.54,周长:31.42 ``` > **提示**: > - 所有子类构造方法中都需要调用 `super(color)` 初始化父类属性 > - `Shape[]` 数组可以存储任意 `Shape` 子类的对象——这是 **多态** 的体现 > - 排序时使用冒泡排序,比较的是 `getArea()` 的返回值 > - 面积/周长计算使用 `double` 类型,输出时使用 `System.out.printf("%.2f", value)` --- ## 第 2 题:抽象类 + 多态——支付系统 Payment ⭐⭐⭐⭐⭐ ### 需求说明 开发一个支付系统,支持多种支付方式。每种支付方式的 **支付逻辑不同**,但都有 **支付** 和 **退款** 两个核心行为。 ### 类设计 **`Payment` 抽象类**(父类): - **私有属性**:`amount`(支付金额,double)、`orderId`(订单号,String) - **有参构造方法**:`Payment(double amount, String orderId)` - **抽象方法**:`void pay()` —— 执行支付 - **抽象方法**:`void refund()` —— 执行退款 - **普通方法**:`showPaymentInfo()` —— 输出 `"订单号:xxx,金额:xxx,支付方式:xxx"` **`WeChatPay` 类**(继承 Payment): - **新增私有属性**:`openId`(微信用户标识,String) - **构造方法**:`WeChatPay(double amount, String orderId, String openId)` - **重写** `pay()` —— 输出 `"微信支付:扫描二维码,扣款 xxx 元"` - **重写** `refund()` —— 输出 `"微信退款:原路退回 xxx 元"` - **重写** `showPaymentInfo()` —— 输出 `"订单号:xxx,金额:xxx,支付方式:微信支付"` **`AliPay` 类**(继承 Payment): - **新增私有属性**:`account`(支付宝账号,String) - **构造方法**:`AliPay(double amount, String orderId, String account)` - **重写** `pay()` —— 输出 `"支付宝支付:跳转支付宝,扣款 xxx 元"` - **重写** `refund()` —— 输出 `"支付宝退款:原路退回 xxx 元"` - **重写** `showPaymentInfo()` —— 输出 `"订单号:xxx,金额:xxx,支付方式:支付宝"` **`CreditCard` 类**(继承 Payment): - **新增私有属性**:`cardNumber`(信用卡号,String) - **构造方法**:`CreditCard(double amount, String orderId, String cardNumber)` - **重写** `pay()` —— 输出 `"信用卡支付:刷卡扣款 xxx 元,额度已冻结"` - **重写** `refund()` —— 输出 `"信用卡退款:解冻额度,退回 xxx 元"` - **重写** `showPaymentInfo()` —— 输出 `"订单号:xxx,金额:xxx,支付方式:信用卡(尾号xxxx)"`(只显示信用卡后4位,使用 `cardNumber.substring(cardNumber.length() - 4)`) ### 测试类要求 **`PaymentTest` 类**: 1. **创建支付对象数组**:创建一个 `Payment[]` 数组,包含至少 4 笔不同的支付记录 **示例数据**: ```java Payment p1 = new WeChatPay(99.9, "ORDER001", "wx_openid_123"); Payment p2 = new AliPay(299.0, "ORDER002", "alipay@example.com"); Payment p3 = new CreditCard(5999.0, "ORDER003", "6222021234567890"); Payment p4 = new WeChatPay(15.5, "ORDER004", "wx_openid_456"); ``` 2. **遍历执行支付**:遍历数组,对每个元素调用 `pay()` 和 `showPaymentInfo()` 方法 3. **遍历执行退款**:遍历数组,对每个元素调用 `refund()` 方法 4. **统计总支付金额**:遍历数组,累加每个元素的 `amount`,输出总金额 ### 思考题 1. 如果新增一个 `ApplePay` 类,需要做哪些工作? 2. 如果 `pay()` 和 `refund()` 不是抽象方法,而是在父类中写一个默认实现,会有什么弊端? 3. 在实际项目中,抽象类常用于定义「**规范**」。让所有子类遵循相同的命名和参数,有什么好处? ### 预期输出(参考) ``` ========== 执行支付 ========== 微信支付:扫描二维码,扣款 99.9 元 订单号:ORDER001,金额:99.9,支付方式:微信支付 支付宝支付:跳转支付宝,扣款 299.0 元 订单号:ORDER002,金额:299.0,支付方式:支付宝 信用卡支付:刷卡扣款 5999.0 元,额度已冻结 订单号:ORDER003,金额:5999.0,支付方式:信用卡(尾号7890) 微信支付:扫描二维码,扣款 15.5 元 订单号:ORDER004,金额:15.5,支付方式:微信支付 ========== 执行退款 ========== 微信退款:原路退回 99.9 元 支付宝退款:原路退回 299.0 元 信用卡退款:解冻额度,退回 5999.0 元 微信退款:原路退回 15.5 元 ========== 支付统计 ========== 总支付金额:6413.4 元 ``` > **提示**: > - 所有子类构造方法中都需要调用 `super(amount, orderId)` 初始化父类属性 > - `showPaymentInfo()` 在父类中已经声明为普通方法(非抽象),子类可以选择重写也可以不重写。这里要求 **重写** 以便输出不同的支付方式名称 > - 抽象类的意义在于:**定义统一规范,强制子类实现特定行为** --- # 第三部分:课外拓展 ## 第 3 题:模板方法设计模式——抽象类经典应用 ⭐⭐⭐⭐⭐ > **说明**:模板方法模式(Template Method Pattern)是抽象类的经典应用场景。**父类定义算法的「骨架」,子类实现特定的「步骤」**。 ### 需求说明 模拟一个「在线考试系统」,每门考试都需要经过以下步骤: 1. **登录验证**(所有考试相同) 2. **发放试卷**(所有考试相同) 3. **考试进行中**(不同科目考试内容不同)← **抽象方法** 4. **收卷评分**(不同科目评分标准不同)← **抽象方法** 5. **公布成绩**(所有考试相同) ### 类设计 **`ExamTemplate` 抽象类**(父类): - **普通方法** `login()` —— 输出 `"考生登录系统,验证身份... 登录成功!"` - **普通方法** `deliverPaper()` —— 输出 `"发放试卷,考试开始!"` - **抽象方法** `void conductExam()` —— 考试进行中(不同科目不同) - **抽象方法** `void gradeExam()` —— 收卷评分(不同科目不同) - **普通方法** `announceResult()` —— 输出 `"成绩公布!请登录系统查看"` - **模板方法** `final void startExam()` —— 按顺序执行完整考试流程: 1. 调用 `login()` 2. 调用 `deliverPaper()` 3. 调用 `conductExam()` 4. 调用 `gradeExam()` 5. 调用 `announceResult()` **`JavaExam` 类**(继承 ExamTemplate): - **重写** `conductExam()` —— 输出 `"Java 考试进行中:编写代码,回答选择题..."` - **重写** `gradeExam()` —— 输出 `"Java 考试评分:检查代码正确性,选择题自动判分..."` **`MathExam` 类**(继承 ExamTemplate): - **重写** `conductExam()` —— 输出 `"数学考试进行中:解答微积分、线性代数题目..."` - **重写** `gradeExam()` —— 输出 `"数学考试评分:答案比对,步骤给分..."` ### 测试类要求 **`ExamTest` 类**: 1. 创建 `JavaExam` 对象,调用 `startExam()` 方法 2. 创建 `MathExam` 对象,调用 `startExam()` 方法 3. 观察:**考试流程的「骨架」由父类定义,子类只需实现「考试内容」和「评分方式」两个步骤**——这就是模板方法模式 ### 预期输出 ``` ========== Java 考试 ========== 考生登录系统,验证身份... 登录成功! 发放试卷,考试开始! Java 考试进行中:编写代码,回答选择题... Java 考试评分:检查代码正确性,选择题自动判分... 成绩公布!请登录系统查看 ========== 数学考试 ========== 考生登录系统,验证身份... 登录成功! 发放试卷,考试开始! 数学考试进行中:解答微积分、线性代数题目... 数学考试评分:答案比对,步骤给分... 成绩公布!请登录系统查看 ``` ### 思考题 1. `startExam()` 方法为什么用 `final` 修饰?如果不加 `final` 会怎样? 2. 模板方法模式的核心思想是什么?和「普通继承 + 方法重写」有什么区别? 3. 你在日常生活中有哪些场景可以用模板方法模式来描述?(例如:做饭流程、工作日报提交流程等) --- ## 📋 题目总览 | 序号 | 所属部分 | 核心知识点 | 难度 | |------|---------|-----------|------| | 练习 1 | 随堂 | 抽象类定义、抽象方法、子类重写抽象方法 | ⭐ | | 练习 2 | 随堂 | **抽象类:计算周长 + 计算面积**、方法重写、super() 调用父类构造方法 | ⭐⭐ | | 练习 3 | 随堂 | 抽象类 + 构造方法 + super()、抽象方法重写 | ⭐⭐ | | 练习 4 | 随堂 | 抽象类设计思想、判断何时使用抽象类 | ⭐⭐ | | 进阶 1 | 进阶 | 抽象类 + 对象数组 + 冒泡排序 + 多态 | ⭐⭐⭐⭐ | | 进阶 2 | 进阶 | 抽象类 + 多态 + 继承 + 综合应用 | ⭐⭐⭐⭐⭐ | | 拓展 3 | 课外 | 模板方法设计模式(抽象类经典应用) | ⭐⭐⭐⭐⭐ |