本课作品
制作三种能力合同 AbilityContracts
蝙蝠和鞘翅玩家并非同一家族,却都能飞。用接口约定“会什么”,用抽象类保存共同骨架,让能力合同小而清楚。 前置背包:继承、多态和方法重写。今天不按长相组队,而是按能力签合同。
开始前:建议先完成第 21–22 课
- Actor抽象共同骨架
- name / label已完成的部分
- role()留给子类回答
01 / 14
开始前:先预测,再让程序回答
本章使用 JDK 21,无需预览功能。故事借用方块世界,示例运行在普通 Java 控制台;数值和规则若是课程模型,都会明确说明。
每个普通示例都是完整独立文件,按框上标注的文件名保存。各示例放在不同文件夹,避免辅助类重名。完整项目分段时,按顺序接在同一个文件中。
终端命令:中文占位符换成实际文件名与类名
javac -encoding UTF-8 示例文件名.java
java 示例类名- 预测:先写预期输出,或画清引用和数据的变化。
- 验证:运行后逐行核对,再只改一个条件。
- 解释:说清结果来自 Java 规则、课程合同还是输入数据。
02 / 14
抽象类留下必须完成的空格
若所有角色都有名字,却没有统一的 role 实现,父类就不该假装给出一个无意义答案。抽象类允许写好共同部分,并明确留下未完成的方法。
- Actor抽象共同骨架
- name / label已完成的部分
- role()留给子类回答
BadAbstract.java · 故意错误示例
abstract class Actor {
protected final String name;
Actor(String name) { this.name = name; }
abstract String role();
public String label() { return role() + " " + name; }
}
public class BadAbstract {
public static void main(String[] args) { Actor a = new Actor("未完成"); }
}03 / 14
抽象方法强制子类回答
抽象方法像试卷上的必答题。子类若仍是具体类,就必须给出正确签名的方法体。
- 具体子类必须完成抽象方法
- @Override核对方法签名
- Bat提供蝙蝠角色
L23Example02.java · 完整独立示例
abstract class Actor {
protected final String name;
Actor(String name) { this.name = name; }
abstract String role();
public String label() { return role() + " " + name; }
}
class Bat extends Actor {
Bat(String name) { super(name); }
@Override String role() { return "蝙蝠"; }
}
public class L23Example02 {
public static void main(String[] args) {
Actor a = new Bat("夜影");
System.out.println(a.label());
}
}运行结果
蝙蝠 夜影BadRole.java · 故意错误示例
abstract class Actor {
protected final String name;
Actor(String name) { this.name = name; }
abstract String role();
public String label() { return role() + " " + name; }
}
class Bat extends Actor { Bat(String name) { super(name); } }
public class BadRole { public static void main(String[] args) { } }04 / 14
接口只描述会不会
接口适合约定跨家族能力。蝙蝠是动物,鞘翅玩家是玩家,但两者都能实现 Flyable;调用者只需知道对方会飞。
- Flyable约定飞行能力
- Bat实现飞行
- ElytraPlayer也实现飞行
L23Example03.java · 完整独立示例
interface Flyable { String fly(); }
class Bat implements Flyable {
@Override public String fly() { return "蝙蝠飞行"; }
}
class ElytraPlayer implements Flyable {
@Override public String fly() { return "玩家滑翔"; }
}
public class L23Example03 {
public static void main(String[] args) {
Flyable[] team = {new Bat(), new ElytraPlayer()};
for (Flyable member : team) System.out.println(member.fly());
}
}运行结果
蝙蝠飞行
玩家滑翔05 / 14
实现方法必须公开
接口方法默认是 public。实现类必须用 public 提供同样可见的入口。
- 接口抽象方法默认 public
- 实现方法不能缩小可见性
- @Override核对同一合同
BadPublic.java · 故意错误示例
interface Flyable { String fly(); }
class Bat implements Flyable { String fly() { return "飞行"; } }
public class BadPublic { public static void main(String[] args) { } }06 / 14
一个类可以签多份合同
Java 类只能直接继承一个父类,却可以实现多个接口。一个飞行炸弹可以同时遵守 Flyable 和 Explodable。
- 一个父类类只能直接继承一个
- 多个接口能力可以组合
- FlyingMine同时会飞和引爆
L23Example05.java · 完整独立示例
interface Flyable { String fly(); }
interface Explodable { String explode(); }
class FlyingMine implements Flyable, Explodable {
@Override public String fly() { return "飞向目标"; }
@Override public String explode() { return "引爆"; }
}
public class L23Example05 {
public static void main(String[] args) {
FlyingMine mine = new FlyingMine();
System.out.println(mine.fly());
System.out.println(mine.explode());
}
}运行结果
飞向目标
引爆07 / 14
接口引用调用能力
方法参数写接口类型,就能接收任何实现者。这是多态从家族关系扩展到能力关系。
- 接口参数只要所需能力
- 任意实现者可接受相同触发
- null本课约定安全跳过
L23Example06.java · 第1/2段,顺序接在同一文件
interface Flyable { String fly(); }
interface Tradable { String trade(String item); }
interface Explodable { int blastPower(); String explode(); }
abstract class Actor {
protected final String name;
Actor(String name) { this.name = name; }
abstract String role();
public String label() { return role() + " " + name; }
}
class Bat extends Actor implements Flyable {
Bat(String name) { super(name); }
@Override String role() { return "蝙蝠"; }
@Override public String fly() { return name + " 飞向高处"; }
}
class Villager extends Actor implements Tradable {
Villager(String name) { super(name); }
@Override String role() { return "村民"; }
@Override public String trade(String item) {
return item == null || item.isBlank()
? name + " 拒绝空交易" : name + " 收下" + item.strip();
}
}
class Creeper extends Actor implements Explodable {
Creeper(String name) { super(name); }
@Override String role() { return "苦力怕"; }
@Override public int blastPower() { return 3; }
@Override public String explode() { return name + " 爆炸"; }
}L23Example06.java · 第2/2段,顺序接在同一文件
public class L23Example06 {
static void triggerFlight(Flyable target) {
if (target != null) System.out.println(target.fly());
}
static void triggerTrade(Tradable target, String item) {
if (target != null) System.out.println(target.trade(item));
}
static void triggerExplosion(Explodable target) {
if (target != null)
System.out.println(target.explode() + " 强度=" + target.blastPower());
}
public static void main(String[] args) {
triggerFlight(new Bat("夜影"));
triggerTrade(new Villager("村长"), "绿宝石");
triggerExplosion(new Creeper("绿豆"));
triggerFlight(null);
}
}运行结果
夜影 飞向高处
村长 收下绿宝石
绿豆 爆炸 强度=308 / 14
小接口比万能杂物袋清楚
把飞行、交易、爆炸塞进一个 MegaAbility,会迫使村民实现不会的 fly。小接口更容易理解和组合。
- Flyable只负责飞行
- Tradable只负责交易
- Explodable只负责爆炸
L23Example07.java · 完整独立示例
interface Flyable { String fly(); }
interface Tradable { String trade(String item); }
interface Explodable { int blastPower(); String explode(); }
abstract class Actor {
protected final String name;
Actor(String name) { this.name = name; }
abstract String role();
public String label() { return role() + " " + name; }
}
class Bat extends Actor implements Flyable {
Bat(String name) { super(name); }
@Override String role() { return "蝙蝠"; }
@Override public String fly() { return name + " 飞向高处"; }
}
class Villager extends Actor implements Tradable {
Villager(String name) { super(name); }
@Override String role() { return "村民"; }
@Override public String trade(String item) {
return item == null || item.isBlank()
? name + " 拒绝空交易" : name + " 收下" + item.strip();
}
}
class Creeper extends Actor implements Explodable {
Creeper(String name) { super(name); }
@Override String role() { return "苦力怕"; }
@Override public int blastPower() { return 3; }
@Override public String explode() { return name + " 爆炸"; }
}
public class L23Example07 {
public static void main(String[] args) {
Tradable merchant = new Villager("村长");
System.out.println(merchant.trade(""));
}
}运行结果
村长 拒绝空交易09 / 14
抽象类与接口比较
抽象类可保存实例状态、构造器和共同实现;接口强调调用者可依赖的能力。一个类常常继承抽象父类,同时实现若干接口。
- 抽象类状态、构造器、共同实现
- 接口调用者依赖的能力
- 组合使用继承骨架并实现接口
| 问题 | 抽象类 | 接口 |
|---|---|---|
| 表达 | 同一家族骨架 | 跨家族能力 |
| 实例字段 | 可以 | 不保存普通实例状态 |
| 构造器 | 可以 | 没有 |
| 数量 | 只能继承一个 | 可实现多个 |
| 典型例子 | Actor | Flyable |
10 / 14
合同测试与故障诊所
接口错误常在编译阶段暴露,行为错误仍要通过测试发现。
- 签名匹配参数不能遗漏
- 空交易明确返回拒绝
- 爆炸强度本课约定3
BadTrade.java · 故意错误示例
interface Tradable { String trade(String item); }
class Villager implements Tradable {
public String trade() { return "交易"; }
}
public class BadTrade { public static void main(String[] args) { } }| 测试 | 输入 | 预期 |
|---|---|---|
| 飞行 | Bat | 返回飞行描述 |
| 交易 | 空物品名 | 返回拒绝提示 |
| 爆炸 | Creeper | 强度为3 |
| 空引用 | null | 安全提示或跳过 |
11 / 14
挑战 蓝图三种能力
挑战 程序叫 AbilityContracts。Bat 会飞,Villager 会交易,Creeper 会爆炸;三者都继承 Actor 并实现各自能力。
- Bat飞行合同
- Villager交易合同
- Creeper爆炸合同
验收条件
- Actor 不能直接 new。
- 三个具体类都实现 role。
- 接口实现方法全部 public。
- 触发方法使用接口参数。
- 交易空字符串时返回拒绝提示。
12 / 14
挑战 实现能力合同
新建 AbilityContracts.java,使用 Java 21 编译运行。
- 先声明三个接口与 Actor
- 再实现逐个完成具体类
- 后触发接口参数统一调用
AbilityContracts.java · 第1/2段,顺序接在同一文件
interface Flyable { String fly(); }
interface Tradable { String trade(String item); }
interface Explodable { int blastPower(); String explode(); }
abstract class Actor {
protected final String name;
Actor(String name) { this.name = name; }
abstract String role();
public String label() { return role() + " " + name; }
}
class Bat extends Actor implements Flyable {
Bat(String name) { super(name); }
@Override String role() { return "蝙蝠"; }
@Override public String fly() { return name + " 飞向高处"; }
}
class Villager extends Actor implements Tradable {
Villager(String name) { super(name); }
@Override String role() { return "村民"; }
@Override public String trade(String item) {
return item == null || item.isBlank()
? name + " 拒绝空交易" : name + " 收下" + item.strip();
}
}
class Creeper extends Actor implements Explodable {
Creeper(String name) { super(name); }
@Override String role() { return "苦力怕"; }
@Override public int blastPower() { return 3; }
@Override public String explode() { return name + " 爆炸"; }
}AbilityContracts.java · 第2/2段,顺序接在同一文件
public class AbilityContracts {
static void triggerFlight(Flyable target) {
if (target != null) System.out.println(target.fly());
}
static void triggerTrade(Tradable target, String item) {
if (target != null) System.out.println(target.trade(item));
}
static void triggerExplosion(Explodable target) {
if (target != null)
System.out.println(target.explode() + " 强度=" + target.blastPower());
}
public static void main(String[] args) {
Bat bat = new Bat("夜影");
Villager villager = new Villager("村长");
Creeper creeper = new Creeper("绿豆");
System.out.println(bat.label());
triggerFlight(bat);
triggerTrade(villager, "绿宝石");
triggerTrade(villager, " ");
triggerExplosion(creeper);
triggerFlight(null);
}
}运行结果
蝙蝠 夜影
夜影 飞向高处
村长 收下绿宝石
村长 拒绝空交易
绿豆 爆炸 强度=313 / 14
拓展任务与通关复盘
每份接口都要附一句合同说明,写清输入、返回值和失败处理。
- 合同说明写清输入与输出
- 失败规则明确拒绝或跳过
- 编译与行为两种检查都需要
三档作业
- 木镐:设计 Swimmable,让海豚和玩家都能实现 swim。
- 铁镐:让一个类同时实现 Flyable 与 Tradable,并用两个接口引用调用。
- 钻石镐:设计红石设备接口,分别实现门、灯和陷阱,并写统一触发器。
| 自评 | 能 | 还要练 |
|---|---|---|
| 我能区分抽象类和接口 | □ | □ |
| 我会使用 implements | □ | □ |
| 我会设计小接口 | □ | □ |
14 / 14
自己的验收记录
漂亮的输出是起点,能解释边界才算掌握。把正常、边界与错误各选一个案例,运行前写预期,运行后留下证据。
| 类别 | 你选择的操作 | 核对重点 |
|---|---|---|
| 正常 | 一个常用输入或一次成功动作 | 结果是否逐行一致 |
| 边界 | 范围端点与相邻值 | 有没有多一步、少一项 |
| 错误 | 非法数量、缺失对象或坏数据 | 是否明确拒绝,状态是否保持 |
- Bug 记录:触发条件、实际现象、原因、最小修复。
- 图示记录:为每条箭头说明起点、终点和含义。
- 改编记录:只改一个规则,同时修改预期和测试。
TAKE IT WITH YOU
下载本课 PDF
完整课文、代码、图解与练习,选择你需要的语言。