20260723-抽象类与接口练习.md 25 KB

2026-07-23 抽象类(Abstract)基础练习(课堂练习用)

说明:以下习题围绕今日授课核心内容——抽象类(abstract)接口(Interface) 展开,涵盖抽象类的定义、特点、抽象方法的重写、抽象类与对象数组/排序的综合应用,以及接口的定义、实现、多实现等入门知识。

题目分为四部分:随堂练习(抽象类入门,4 题)、进阶练习(综合挑战,2 题)、课外拓展(1 题)和 接口入门练习(3 题)。


第一部分:随堂练习

以下练习围绕抽象类的基础概念和用法,由浅入深。


练习 1:抽象类入门——Instrument 乐器体系

难度:⭐
知识点:抽象类的定义、抽象方法、子类重写抽象方法

创建一个乐器抽象体系:

Instrument 抽象类(父类):

  • 抽象方法play() —— 演奏乐器(无返回值、无参数)

Piano(继承 Instrument):

  • 重写 play() —— 输出 "弹钢琴:叮咚叮咚~"

Violin(继承 Instrument):

  • 重写 play() —— 输出 "拉小提琴:咿呀咿呀~"

Drum(继承 Instrument):

  • 重写 play() —— 输出 "打鼓:咚咚咚!"

测试类要求

  1. 创建 PianoViolinDrum 各一个对象
  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 抽象类及其子类(CircleRectangleTriangle),创建一个图形数组,按 面积从大到小 排序。

类设计

直接复用练习 2 中的 Shape(抽象类)、CircleRectangleTriangle 类。

测试类要求

ShapeSortTest

  1. 创建图形数组:创建一个 Shape[] 数组,包含至少 5 个不同的图形对象

示例数据

   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
  1. 遍历输出所有图形信息:调用每个元素的 showInfo() 方法

  2. 按面积从大到小排序:使用冒泡排序,按 getArea() 返回值降序排列

  3. 排序后再次输出所有图形信息

  4. 查找面积最大的图形:遍历数组,输出面积最大的图形信息

思考题

  1. 数组类型声明为 Shape[],为什么能存储 CircleRectangleTriangle 等不同类型的对象?这体现了 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 笔不同的支付记录

示例数据

   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");
  1. 遍历执行支付:遍历数组,对每个元素调用 pay()showPaymentInfo() 方法

  2. 遍历执行退款:遍历数组,对每个元素调用 refund() 方法

  3. 统计总支付金额:遍历数组,累加每个元素的 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. 你在日常生活中有哪些场景可以用模板方法模式来描述?(例如:做饭流程、工作日报提交流程等)

第四部分:接口入门练习

以下练习围绕 接口(Interface) 的基础概念和用法,由浅入深。


练习 5:接口入门——Playable 播放器接口

难度:⭐
知识点:接口定义、接口方法、implements 实现接口

创建一个播放器接口体系:

Playable 接口

  • 抽象方法void play() —— 播放
  • 抽象方法void pause() —— 暂停
  • 抽象方法void stop() —— 停止

MP3Player(实现 Playable):

  • 私有属性songName(歌曲名,String)
  • 构造方法MP3Player(String songName)
  • 实现 play() —— 输出 "正在播放:xxx"
  • 实现 pause() —— 输出 "暂停播放:xxx"
  • 实现 stop() —— 输出 "停止播放:xxx"

VideoPlayer(实现 Playable):

  • 私有属性movieName(电影名,String)
  • 构造方法VideoPlayer(String movieName)
  • 实现 play() —— 输出 "正在播放视频:xxx"
  • 实现 pause() —— 输出 "暂停播放视频:xxx"
  • 实现 stop() —— 输出 "停止播放视频:xxx"

测试类要求

  1. 创建 MP3Player 对象("青花瓷"),依次调用 play()pause()stop()
  2. 创建 VideoPlayer 对象("哪吒"),依次调用 play()pause()stop()
  3. 思考:Playable 接口中三个方法的默认修饰符是什么?

预期输出

正在播放:青花瓷
暂停播放:青花瓷
停止播放:青花瓷
正在播放视频:哪吒
暂停播放视频:哪吒
停止播放视频:哪吒

练习 6:USB 充电器接口——Charger 充电规范 ⭐(配合课堂代码)

难度:⭐⭐
知识点:接口定义、implements 实现、类继承父类的同时实现接口

参照课堂上的 USB 接口标准示例,创建一个充电器接口体系:

Charger 接口

  • 抽象方法void connect() —— 连接设备
  • 抽象方法void charging() —— 正在充电
  • 抽象方法void disconnect() —— 断开连接

Device(父类):

  • 私有属性brand(品牌,String)、model(型号,String)
  • 构造方法Device(String brand, String model)
  • getter/setter
  • 普通方法showInfo() —— 输出 "设备:xxx 品牌:xxx"

Phone(继承 Device 并实现 Charger):

  • 新增私有属性batteryCapacity(电池容量,int,单位 mAh)
  • 构造方法Phone(String brand, String model, int batteryCapacity)
  • 实现 connect() —— 输出 "手机已连接充电器"
  • 实现 charging() —— 输出 "手机正在充电,电池容量:xxx mAh"
  • 实现 disconnect() —— 输出 "手机已断开充电器"

Laptop(继承 Device 并实现 Charger):

  • 新增私有属性power(功率,int,单位 W)
  • 构造方法Laptop(String brand, String model, int power)
  • 实现 connect() —— 输出 "笔记本电脑已连接电源"
  • 实现 charging() —— 输出 "笔记本电脑正在充电,功率:xxx W"
  • 实现 disconnect() —— 输出 "笔记本电脑已断开电源"

测试类要求

  1. 创建 Phone 对象("华为", "Mate60", 5000),调用 showInfo(),然后依次调用充电三步骤
  2. 创建 Laptop 对象("联想", "ThinkPad", 65),调用 showInfo(),然后依次调用充电三步骤

预期输出

设备:Mate60 品牌:华为
手机已连接充电器
手机正在充电,电池容量:5000 mAh
手机已断开充电器
设备:ThinkPad 品牌:联想
笔记本电脑已连接电源
笔记本电脑正在充电,功率:65 W
笔记本电脑已断开电源

练习 7:接口多实现——多功能设备

难度:⭐⭐⭐
知识点:一个类实现多个接口、接口的扩展性

创建一个多功能设备,同时实现 拍照播放 两个接口。

Camera 接口

  • 抽象方法void takePhoto() —— 拍照
  • 抽象方法void recordVideo() —— 录像

Playable 接口(复用练习 5 的定义):

  • 抽象方法void play() —— 播放
  • 抽象方法void pause() —— 暂停
  • 抽象方法void stop() —— 停止

SmartPhone(实现 Camera 和 Playable 两个接口):

  • 私有属性brand(品牌,String)、model(型号,String)
  • 构造方法SmartPhone(String brand, String model)
  • 实现 takePhoto() —— 输出 "xxx xxx 拍照:咔嚓!"
  • 实现 recordVideo() —— 输出 "xxx xxx 录像:开始录制视频..."
  • 实现 play() —— 输出 "xxx xxx 播放音乐"
  • 实现 pause() —— 输出 "xxx xxx 暂停播放"
  • 实现 stop() —— 输出 "xxx xxx 停止播放"

注意:xxx xxx 部分使用品牌和型号拼接,如 "苹果 iPhone15"

测试类要求

  1. 创建 SmartPhone 对象("苹果", "iPhone15")
  2. 调用拍照功能:takePhoto()recordVideo()
  3. 调用播放功能:play()pause()stop()
  4. 观察:一个类实现了两个接口,同时拥有了拍照能力和播放能力——这就是接口做功能拓展的优势

思考题

  1. 如果使用抽象类来实现同样的功能,你会怎么设计?能避免「单继承」的限制吗?
  2. 接口中的方法默认使用什么修饰符?可以省略不写吗?
  3. 接口中的变量默认使用什么修饰符?可以修改接口中变量的值吗?

预期输出

苹果 iPhone15 拍照:咔嚓!
苹果 iPhone15 录像:开始录制视频...
苹果 iPhone15 播放音乐
苹果 iPhone15 暂停播放
苹果 iPhone15 停止播放

📋 题目总览

序号 所属部分 核心知识点 难度
练习 1 随堂 抽象类定义、抽象方法、子类重写抽象方法
练习 2 随堂 抽象类:计算周长 + 计算面积、方法重写、super() 调用父类构造方法 ⭐⭐
练习 3 随堂 抽象类 + 构造方法 + super()、抽象方法重写 ⭐⭐
练习 4 随堂 抽象类设计思想、判断何时使用抽象类 ⭐⭐
进阶 1 进阶 抽象类 + 对象数组 + 冒泡排序 + 多态 ⭐⭐⭐⭐
进阶 2 进阶 抽象类 + 多态 + 继承 + 综合应用 ⭐⭐⭐⭐⭐
拓展 3 课外 模板方法设计模式(抽象类经典应用) ⭐⭐⭐⭐⭐
练习 5 接口入门 接口定义、接口方法、implements 实现接口
练习 6 接口入门 接口实现、类继承父类同时实现接口(Charger 充电规范) ⭐⭐
练习 7 接口入门 一个类实现多个接口、接口做功能拓展 ⭐⭐⭐