本课作品
制作安全箱子
会用 private 保护对象字段;能通过方法提供受控读取和修改;能用校验守住容量与非负库存;能避免把内部可变数组直接交给外部代码。 前置背包:类、对象、构造器、方法参数与返回值。今天要给数据装门、装锁,还要设计真正好用的按钮。
开始前:建议先完成第 17–19 课
- 请求存入或取出数量
- 校验不超容量,不负库存
- 状态成功才写入,失败不变
01 / 15
开始前:把示例放对地方
本章使用 JDK 21,不需要预览功能。每个普通示例都给出完整程序,按标注的文件名保存。多个示例各放在自己的文件夹,避免辅助类同名。
终端命令:把中文占位符替换成实际名称
javac -encoding UTF-8 示例文件名.java
java 示例类名类名区分大小写。完整挑战代码若分成多段,请按顺序接在同一个文件中,不要为续段另建类。运行核对只列程序输出;需要输入的地方另给输入序列。
02 / 15
封装让规则只有一个入口
public 字段像没有门的钻石仓库:任何代码都能把库存改成 -999。封装把数据和维护数据的规则放在同一个类里。
- 字段private保存内部状态
- 方法公开受控入口
- 校验先判断合法性
- 规则0到容量内的库存
L20Example01.java · 完整独立示例
class Chest { public int diamonds; }
public class L20Example01 {
public static void main(String[] args) {
Chest chest = new Chest();
chest.diamonds = -999;
System.out.println(chest.diamonds);
}
}运行结果
-999封装目标
- 外部不能随意写字段。
- 所有修改经过同一套检查。
- 类对外暴露清楚而精简的方法。
- 通过校验的公开操作应保持对象合法状态。
03 / 15
private 的可见范围
本章把箱子与调用程序写成两个独立类:箱子内部可以维护 private 字段,调用程序使用公开方法。Java 还允许同一最外层类及其嵌套类之间访问 private 成员;不要用这个例外检验外部访问规则。
- 箱子内部可维护私有字段
- 独立外部类不能直接写私有字段
- 公开方法提供允许的操作
- 例外同一外层类的嵌套成员
BadPrivate.java · 故意错误示例
class Wallet { private int emeralds; }
public class BadPrivate {
public static void main(String[] args) {
Wallet wallet = new Wallet();
wallet.emeralds = 99;
}
}04 / 15
getter 提供受控读取
外部若需要查看状态,可提供 getter 或更有意义的查询方法。不是每个字段都必须配一个 getter。
- getCount读取当前库存
- remainingSpace读取剩余空间
- 查询不修改状态
- 最少公开只提供真正需要的信息
L20Example03.java · 完整独立示例
class Chest {
private final String item;
private final int capacity;
private int count;
public Chest(String item, int capacity) {
this.item = item == null || item.isBlank() ? "未知物品" : item.trim();
this.capacity = Math.max(1, capacity);
}
public boolean deposit(int amount) {
if (amount <= 0 || amount > capacity - count) return false;
count += amount;
return true;
}
public boolean withdraw(int amount) {
if (amount <= 0 || amount > count) return false;
count -= amount;
return true;
}
public int getCount() { return count; }
public int remainingSpace() { return capacity - count; }
public String describe() { return item + " " + count + "/" + capacity; }
}
public class L20Example03 {
public static void main(String[] args) {
Chest chest = new Chest("钻石", 64);
System.out.println(chest.getCount());
System.out.println(chest.remainingSpace());
}
}运行结果
0
6405 / 15
行为方法比万能 setter 更安全
setCount(-999) 让调用者承担所有规则。deposit 和 withdraw 则表达真实动作,并能集中检查。
数量合法吗?
| 条件结果 | 执行路径 |
|---|---|
| true | 写入数量,返回 true |
| false | 保持原样,返回 false |
↓ 调用者检查结果
L20Example04.java · 完整独立示例
class Chest {
private final String item;
private final int capacity;
private int count;
public Chest(String item, int capacity) {
this.item = item == null || item.isBlank() ? "未知物品" : item.trim();
this.capacity = Math.max(1, capacity);
}
public boolean deposit(int amount) {
if (amount <= 0 || amount > capacity - count) return false;
count += amount;
return true;
}
public boolean withdraw(int amount) {
if (amount <= 0 || amount > count) return false;
count -= amount;
return true;
}
public int getCount() { return count; }
public int remainingSpace() { return capacity - count; }
public String describe() { return item + " " + count + "/" + capacity; }
}
public class L20Example04 {
public static void main(String[] args) {
Chest chest = new Chest("钻石", 64);
System.out.println(chest.deposit(12));
System.out.println(chest.withdraw(3));
System.out.println(chest.getCount());
}
}运行结果
true
true
906 / 15
校验守住合法范围
失败操作不应修改对象。先检查所有条件,再写字段。
数量合法吗?
| 条件结果 | 执行路径 |
|---|---|
| true | 写入数量,返回 true |
| false | 保持原样,返回 false |
↓ 调用者检查结果
L20Example05.java · 完整独立示例
class Chest {
private final String item;
private final int capacity;
private int count;
public Chest(String item, int capacity) {
this.item = item == null || item.isBlank() ? "未知物品" : item.trim();
this.capacity = Math.max(1, capacity);
}
public boolean deposit(int amount) {
if (amount <= 0 || amount > capacity - count) return false;
count += amount;
return true;
}
public boolean withdraw(int amount) {
if (amount <= 0 || amount > count) return false;
count -= amount;
return true;
}
public int getCount() { return count; }
public int remainingSpace() { return capacity - count; }
public String describe() { return item + " " + count + "/" + capacity; }
}
public class L20Example05 {
public static void main(String[] args) {
Chest chest = new Chest("钻石", 64);
chest.deposit(12);
int before = chest.getCount();
boolean ok = chest.deposit(Integer.MAX_VALUE);
System.out.println(!ok && chest.getCount() == before);
}
}运行结果
true07 / 15
boolean 汇报操作结果
修改方法返回 boolean,可以让调用者知道操作是否完成,而不必猜测字段变化。
- true表示操作完成
- false表示拒绝且不变
- 提示调用者选择反馈
- 测试同时检查结果与状态
L20Example06.java · 完整独立示例
class Chest {
private final String item;
private final int capacity;
private int count;
public Chest(String item, int capacity) {
this.item = item == null || item.isBlank() ? "未知物品" : item.trim();
this.capacity = Math.max(1, capacity);
}
public boolean deposit(int amount) {
if (amount <= 0 || amount > capacity - count) return false;
count += amount;
return true;
}
public boolean withdraw(int amount) {
if (amount <= 0 || amount > count) return false;
count -= amount;
return true;
}
public int getCount() { return count; }
public int remainingSpace() { return capacity - count; }
public String describe() { return item + " " + count + "/" + capacity; }
}
public class L20Example06 {
public static void main(String[] args) {
Chest chest = new Chest("钻石", 64);
if (chest.deposit(12)) System.out.println("已存入");
int before = chest.getCount();
boolean ok = chest.withdraw(999);
System.out.println(!ok && chest.getCount() == before);
}
}运行结果
已存入
true08 / 15
容量与非负库存
SecureChest 的核心不变条件是 0 <= count <= capacity。构造器先建立合法容量,之后每个方法继续守住它。
- 0合法的空箱
- capacity合法的满箱
- 负数不允许库存为负
- 越界不允许超过容量
L20Example07.java · 完整独立示例
class Chest {
private final String item;
private final int capacity;
private int count;
public Chest(String item, int capacity) {
this.item = item == null || item.isBlank() ? "未知物品" : item.trim();
this.capacity = Math.max(1, capacity);
}
public boolean deposit(int amount) {
if (amount <= 0 || amount > capacity - count) return false;
count += amount;
return true;
}
public boolean withdraw(int amount) {
if (amount <= 0 || amount > count) return false;
count -= amount;
return true;
}
public int getCount() { return count; }
public int remainingSpace() { return capacity - count; }
public String describe() { return item + " " + count + "/" + capacity; }
}
public class L20Example07 {
public static void main(String[] args) {
Chest chest = new Chest("钻石", 0);
System.out.println(chest.remainingSpace());
System.out.println(chest.deposit(1));
System.out.println(chest.deposit(1));
System.out.println(chest.withdraw(1));
}
}运行结果
1
true
false
true| 输入 | 是否允许 | 原因 |
|---|---|---|
| 存入0 | 否 | 数量必须大于0 |
| 存入到刚好满 | 是 | 等于容量合法 |
| 取出全部 | 是 | 库存可以为0 |
| 取出超过库存 | 否 | 不能为负数 |
09 / 15
别暴露内部可变数组
private 数组若被 getter 原样返回,外部仍能通过引用修改内部内容。字段虽然 private,后门却敞开了。
- 内部数组private仍是可变对象
- 直接返回会共享内部引用
- copyOf新数组,元素引用浅复制
- 安全性String不可变,对象仍需检查
L20Example08.java · 完整独立示例
class ItemBox {
private final String[] items = {"钻石", "火把"};
public String[] getItemsCopy() {
return java.util.Arrays.copyOf(items, items.length);
}
public String getItem(int index) {
if (index < 0 || index >= items.length) return null;
return items[index];
}
}
public class L20Example08 {
public static void main(String[] args) {
ItemBox box = new ItemBox();
String[] copy = box.getItemsCopy();
copy[0] = "泥土";
System.out.println(copy[0]);
System.out.println(box.getItem(0));
System.out.println(box.getItem(9));
}
}运行结果
泥土
钻石
null10 / 15
设计箱子的公开接口
这里的“公开接口”指调用者可使用的方法集合,并非 Java 的 interface 关键字。先写调用者需要做什么,再设计最少的方法集合。
- deposit受控存入
- withdraw受控取出
- getCount库存查询
- remainingSpace余量查询
| 公开方法 | 输入 | 返回 | 保证 |
|---|---|---|---|
| deposit | 正数量 | boolean | 不超过容量 |
| withdraw | 正数量 | boolean | 不低于0 |
| getCount | 无 | int | 只读取库存 |
| remainingSpace | 无 | int | 只读取空间 |
| describe | 无 | String | 提供摘要 |
11 / 15
三类测试锁住规则
封装测试不仅看返回值,还要通过公开查询确认对象状态。
- 正常存入12个
- 边界正好存满、正好取空
- 错误满箱存入、空箱取出
- 状态拒绝后不得改变
L20Example10.java · 完整独立示例
class Chest {
private final String item;
private final int capacity;
private int count;
public Chest(String item, int capacity) {
this.item = item == null || item.isBlank() ? "未知物品" : item.trim();
this.capacity = Math.max(1, capacity);
}
public boolean deposit(int amount) {
if (amount <= 0 || amount > capacity - count) return false;
count += amount;
return true;
}
public boolean withdraw(int amount) {
if (amount <= 0 || amount > count) return false;
count -= amount;
return true;
}
public int getCount() { return count; }
public int remainingSpace() { return capacity - count; }
public String describe() { return item + " " + count + "/" + capacity; }
}
public class L20Example10 {
public static void main(String[] args) {
Chest chest = new Chest("钻石", 64);
System.out.println(chest.deposit(12));
System.out.println(chest.deposit(52));
System.out.println(chest.deposit(1));
System.out.println(chest.getCount());
System.out.println(chest.withdraw(64));
System.out.println(chest.withdraw(1));
System.out.println(chest.getCount());
}
}运行结果
true
true
false
64
true
false
0| 测试 | 操作 | 预期 |
|---|---|---|
| 正常 | 容量64存入12 | true 库存12 |
| 边界 | 再存入52 | true 库存64 |
| 错误 | 满箱再存1 | false 库存64 |
| 边界 | 取出64 | true 库存0 |
| 错误 | 空箱取1 | false 库存0 |
12 / 15
封装故障诊所
编译器能发现类外直接访问 private,却不能自动发现 setter 漏校验或数组引用泄漏。
- 直接访问编译器阻止
- 漏校验编译器不会代做
- 先改后拒绝已经破坏状态
- 数组泄漏公开了内部可变引用
L20Example11.java · 完整独立示例
class BrokenChest {
private int count = 0;
public boolean withdraw(int n) {
count -= n;
return count >= 0;
}
public int getCount() { return count; }
}
public class L20Example11 {
public static void main(String[] args) {
BrokenChest c = new BrokenChest();
System.out.println(c.withdraw(3));
System.out.println(c.getCount());
}
}运行结果
false
-313 / 15
挑战 实现 安全箱子
挑战 程序叫 SecureChest。箱子保存一种物品、容量和数量,外部只能使用公开方法。
- 构造器初始化容量与名字
- deposit余量足够才加库存
- withdraw库存足够才扣除
- 查询通过公开方法验证
SecureChest.java · 完整独立示例
class Chest {
private final String item;
private final int capacity;
private int count;
public Chest(String item, int capacity) {
this.item = item == null || item.isBlank() ? "未知物品" : item.trim();
this.capacity = Math.max(1, capacity);
}
public boolean deposit(int amount) {
if (amount <= 0 || amount > capacity - count) return false;
count += amount;
return true;
}
public boolean withdraw(int amount) {
if (amount <= 0 || amount > count) return false;
count -= amount;
return true;
}
public int getCount() { return count; }
public int remainingSpace() { return capacity - count; }
public String describe() { return item + " " + count + "/" + capacity; }
}
public class SecureChest {
public static void main(String[] args) {
Chest chest = new Chest("钻石", 64);
System.out.println(chest.deposit(12));
System.out.println(chest.deposit(52));
System.out.println(chest.deposit(1));
System.out.println(chest.describe());
System.out.println(chest.withdraw(64));
System.out.println(chest.withdraw(1));
System.out.println(chest.describe());
System.out.println(chest.remainingSpace());
}
}运行结果
true
true
false
钻石 64/64
true
false
钻石 0/64
6414 / 15
拓展任务与单元复盘
第17至20课完成了面向对象第一阶段:蓝图、引用、出生和封装。作业要同时提交接口表与测试表。
- 木镐Mob生命改为private
- 铁镐维护0到20饥饿值
- 钻石镐设计有状态的密码箱
- 复盘封装是约束代码访问
三档作业
- 木镐:把 Mob 的 health 改为 private,提供 takeDamage、heal 和 getHealth。
- 铁镐:设计 FoodBar,饥饿值始终保持0到20,非法操作返回false。
- 钻石镐:设计三次输错会锁定的密码箱,不允许外部直接修改密码和锁定状态。
| 自评 | 能 | 还要练 |
|---|---|---|
| 我会使用private | □ | □ |
| 我会设计行为方法 | □ | □ |
| 我会验证失败不改状态 | □ | □ |
15 / 15
自己的验收记录
运行之前写预期,运行之后记录实际结果。正常案例说明程序会做事;边界案例说明它知道何时停;错误案例说明它能拒绝不合理的请求。
| 类别 | 你选择的输入 | 预期与实际 |
|---|---|---|
| 正常 | 一个常用值或一次成功操作 | 是否逐行一致? |
| 边界 | 最小值、最大值及相邻值 | 有没有少一格、多一步? |
| 错误 | 非法数量、缺失对象或错误语法 | 是否明确拒绝?状态是否保持? |
- 把一个 Bug 写成:触发条件、现象、原因、最小修复。
- 整理一个值得记住的规则,用自己的话解释,不能只复制定义。
- 改变一个示例,保留测试记录,让结果证明你的理解。
TAKE IT WITH YOU
下载本课 PDF
完整课文、代码、图解与练习,选择你需要的语言。