本课作品
制作可交互的背包管理员
会把变量 条件 循环 方法和数组装进一个可运行项目;能设计菜单循环和安全输入;能完成查看 添加 删除 查找四项功能;能用测试表验收背包管理员。 前置背包:第1至15课的变量、输入、条件、循环、方法与数组。今天不是再学一块新砖,而是把旧砖盖成能住人的程序。
开始前:建议先完成第 1–15 课
| 格子 | 物品 | 数量 |
|---|---|---|
| 0 | 木头 | 12 |
| 1 | 圆石 | 32 |
| 2 | null | 0 |
01 / 16
开始前:把示例放对地方
本章使用 JDK 21,不需要预览功能。每个普通示例都给出完整程序,按标注的文件名保存。多个示例各放在自己的文件夹,避免辅助类同名。
终端命令:把中文占位符替换成实际名称
javac -encoding UTF-8 示例文件名.java
java 示例类名类名区分大小写。完整挑战代码若分成多段,请按顺序接在同一个文件中,不要为续段另建类。运行核对只列程序输出;需要输入的地方另给输入序列。
02 / 16
项目地图与验收条件
前十五课的知识今天组队打 挑战。挑战 不是末影龙,而是一个会不停问“请选择菜单”的控制台程序。
最终程序能查看、添加、删除、查找物品,并且遇到错误输入不会原地爆炸。
- 菜单重复接收操作
- 配对数组同格保存名字与数量
- 规则方法查看、添加、删除、查找
- 验收正常、边界、错误三类
完成标准
- 菜单会重复显示,选择0才退出。
- 物品名和数量使用相同索引保存。
- 添加和删除都检查数量与背包边界。
- 至少准备正常、边界、错误三类测试。
03 / 16
背包的数据模型
一个格子要保存物品名和数量。当前阶段还没学习自定义物品类,所以先用两个等长数组配对。
| 索引 | items[i] | counts[i] |
|---|---|---|
| 0 | 木头 | 12 |
| 1 | 圆石 | 32 |
| 2 | null | 0 |
L16Example02.java · 完整独立示例
public class L16Example02 {
public static void main(String[] args) {
String[] items = {"木头", "圆石", null};
int[] counts = {12, 32, 0};
int slot = 1;
System.out.println(items[slot] + " x" + counts[slot]);
}
}运行结果
圆石 x3204 / 16
菜单循环控制程序
菜单型程序通常不知道玩家要操作几次,所以使用 while。每轮显示菜单、读取选择、执行功能,再回到入口。
| 步骤 | 操作 |
|---|---|
| 1 · 只执行一次 | running = true |
| 2 · 检查条件 | running 为 true |
| 3 · true 时执行 | 显示并处理 |
| 4 · 更新,回到步骤 2 | 进入下一轮 |
条件为 false 时退出循环。
菜单在每一轮显示;退出设 running = false,输入结束也安全退出。
L16Example03.java · 完整独立示例
public class L16Example03 {
public static void main(String[] args) {
java.util.Scanner scanner = new java.util.Scanner(System.in);
boolean running = true;
while (running && scanner.hasNextLine()) {
String choice = scanner.nextLine().trim();
switch (choice) {
case "1" -> System.out.println("打开背包");
case "0" -> running = false;
default -> System.out.println("没有这个按钮");
}
}
System.out.println("已退出");
}
}运行结果
打开背包
没有这个按钮
已退出05 / 16
安全读取整数
Scanner 的 nextLine 先得到文本,再尝试转换为整数。输入“钻石”时,程序应给提示,而不是用异常把玩家踢出世界。
- 文本读取并去除边缘空白
- parseInt尝试转成int
- 正整数成功且大于0才接受
- 非法-1报告失败
L16Example04.java · 完整独立示例
public class L16Example04 {
static int positive(String text) {
try {
int n = Integer.parseInt(text.trim());
return n > 0 ? n : -1;
} catch (NumberFormatException ex) { return -1; }
}
public static void main(String[] args) {
System.out.println(positive(" 12 "));
System.out.println(positive("钻石"));
System.out.println(positive("0"));
System.out.println(positive("999999999999"));
}
}运行结果
12
-1
-1
-106 / 16
查看背包
查看功能需要打印索引,所以普通 for 比增强 for 更合适。空格统一显示为“空位”。
- 有物品打印名字与数量
- 空位统一显示空位
- 索引显示0到length减1
- 数组必须等长且配对合法
L16Example05.java · 完整独立示例
public class L16Example05 {
static void show(String[] items, int[] counts) {
for (int i = 0; i < items.length; i++) {
String text = items[i] == null ? "空位" : items[i] + " x" + counts[i];
System.out.println(i + ": " + text);
}
}
public static void main(String[] args) {
String[] items = {"木头", null};
int[] counts = {12, 0};
show(items, counts);
}
}运行结果
0: 木头 x12
1: 空位07 / 16
查找物品
搜索方法返回索引,让添加、删除和查询功能都能复用。目标名称先检查 null。
- target先排除null
- equals按文字内容比较
- 找到返回首个索引
- 未找到返回-1
L16Example06.java · 完整独立示例
public class L16Example06 {
static final int MAX_STACK = 64;
static int find(String[] items, String target) {
if (target == null) return -1;
for (int i = 0; i < items.length; i++)
if (target.equals(items[i])) return i;
return -1;
}
public static void main(String[] args) {
String[] items = {"木头", "圆石", null};
System.out.println(find(items, "圆石"));
System.out.println(find(items, "钻石"));
System.out.println(find(items, null));
}
}运行结果
1
-1
-108 / 16
添加与堆叠
添加物品有两条路线:背包已有该物品时增加数量;没有时放进第一个空格。每格最多64个。
- 已存在使用已有格子
- 新物品找第一个空格
- 余量先检查64减当前数量
- 成功同步写入名字和数量
L16Example07.java · 完整独立示例
public class L16Example07 {
static final int MAX_STACK = 64;
static int find(String[] items, String target) {
if (target == null) return -1;
for (int i = 0; i < items.length; i++)
if (target.equals(items[i])) return i;
return -1;
}
static int empty(String[] items) {
for (int i = 0; i < items.length; i++)
if (items[i] == null) return i;
return -1;
}
static boolean add(String[] items, int[] counts, String item, int n) {
if (item == null || item.isBlank() || n <= 0) return false;
int slot = find(items, item);
if (slot < 0) slot = empty(items);
if (slot < 0 || n > MAX_STACK - counts[slot]) return false;
items[slot] = item;
counts[slot] += n;
return true;
}
public static void main(String[] args) {
String[] items = {"木头", null};
int[] counts = {12, 0};
System.out.println(add(items, counts, "木头", 5));
System.out.println(add(items, counts, "木头", Integer.MAX_VALUE));
System.out.println(counts[0]);
}
}运行结果
true
false
1709 / 16
删除与清空格子
删除方法先找到物品,再确认请求数量合法。减到0时必须同时清空物品名。
- 查找未找到就拒绝
- 数量大于0且不超库存
- 扣除检查通过后再修改
- 清空归零时名字也变null
L16Example08.java · 完整独立示例
public class L16Example08 {
static final int MAX_STACK = 64;
static int find(String[] items, String target) {
if (target == null) return -1;
for (int i = 0; i < items.length; i++)
if (target.equals(items[i])) return i;
return -1;
}
static boolean remove(String[] items, int[] counts, String item, int n) {
int slot = find(items, item);
if (slot < 0 || n <= 0 || n > counts[slot]) return false;
counts[slot] -= n;
if (counts[slot] == 0) items[slot] = null;
return true;
}
public static void main(String[] args) {
String[] items = {"木头"};
int[] counts = {12};
System.out.println(remove(items, counts, "木头", 13));
System.out.println(counts[0]);
System.out.println(remove(items, counts, "木头", 12));
System.out.println(items[0] + ":" + counts[0]);
}
}运行结果
false
12
true
null:010 / 16
方法分工
main 负责菜单与流程,功能方法负责数据规则。这样出错时能快速判断是哪一位“村民”交错了货。
- 显示show负责输出
- 查找find返回索引
- 修改add和remove维护规则
- 输入main读取并安排操作
| 方法 | 输入 | 返回或效果 |
|---|---|---|
| show | 两个数组 | 打印每格状态 |
| find | 物品数组、名称 | 索引或-1 |
| add | 数组、名称、数量 | 成功或失败 |
| remove | 数组、名称、数量 | 成功或失败 |
| positive | 一行文本 | 正整数或-1 |
11 / 16
三类测试
项目不是“能运行一次”就通关。每项功能至少准备一个正常测试、一个边界测试和一个错误测试。
- 正常功能能完成
- 边界恰满、恰空
- 错误非法数量与未知命令
- 回归修复后重跑原测试
| 功能 | 输入 | 预期 |
|---|---|---|
| 添加 | 木头 5 | 数量增加5 |
| 添加 | 圆石 40 到已有32 | 拒绝 超过64 |
| 删除 | 木头 0 | 拒绝 数据不变 |
| 查找 | 不存在的钻石 | 返回-1 |
| 菜单 | x | 提示没有按钮 |
12 / 16
项目故障诊所
项目变大后,不要凭感觉乱改。先记录输入和现象,再定位负责该规则的方法。
- 死循环退出未改变条件
- 配对错名字与数量不同步
- 越界索引不合法
- 超量检查失败必须不改数据
L16Example11.java · 完整独立示例
public class L16Example11 {
public static void main(String[] args) {
String[] items = {"木头"};
int[] counts = {12};
counts[0] = 0;
System.out.println(items[0] + " x" + counts[0]);
}
}运行结果
木头 x013 / 16
挑战 蓝图
挑战 程序叫 InventoryManager。完整版本包含可重复操作的控制台菜单,查看、添加、删除、查找都有真实实现。先完成数据方法,再接入菜单。
- 先实现查看与查找
- 再实现添加与删除
- 接菜单命令调用真实功能
- 验收失败时两个数组不变
验收顺序
- 显示初始背包。
- 添加5个木头并确认数量。
- 加入3个钻石并确认占用空格。
- 删除全部圆石并确认格子清空。
- 尝试添加导致超过64的数量,必须失败。
| 规则 | 合法范围 | 失败表现 |
|---|---|---|
| 索引 | 0到length-1 | 不访问数组 |
| 数量 | 大于0 | 返回false |
| 堆叠 | 最多64 | 数据不变 |
| 空格 | 至少一个null | 返回false |
14 / 16
挑战 实现
新建 InventoryManager.java。程序不依赖真实 Minecraft 接口,可直接在 Java 21 控制台编译运行。
- 基础数组与查找
- 规则余量检查与清空
- 菜单while与switch
- 输入异常处理与结束检查
InventoryManager.java · 第1/3段,顺序接在同一文件
public class InventoryManager {
static final int MAX_STACK = 64;
static int find(String[] items, String target) {
if (target == null) return -1;
for (int i = 0; i < items.length; i++)
if (target.equals(items[i])) return i;
return -1;
}
static int empty(String[] items) {
for (int i = 0; i < items.length; i++)
if (items[i] == null) return i;
return -1;
}
static boolean add(String[] items, int[] counts, String item, int n) {
if (item == null || item.isBlank() || n <= 0) return false;
int slot = find(items, item);
if (slot < 0) slot = empty(items);
if (slot < 0 || n > MAX_STACK - counts[slot]) return false;
items[slot] = item;
counts[slot] += n;
return true;
}
static boolean remove(String[] items, int[] counts, String item, int n) {
int slot = find(items, item);
if (slot < 0 || n <= 0 || n > counts[slot]) return false;
counts[slot] -= n;
if (counts[slot] == 0) items[slot] = null;
return true;InventoryManager.java · 第2/3段,顺序接在同一文件
}
static void show(String[] items, int[] counts) {
for (int i = 0; i < items.length; i++) {
String text = items[i] == null ? "空位" : items[i] + " x" + counts[i];
System.out.println(i + ": " + text);
}
}
static int positive(String text) {
try {
int n = Integer.parseInt(text.trim());
return n > 0 ? n : -1;
} catch (NumberFormatException ex) { return -1; }
}
public static void main(String[] args) {
String[] items = {"木头", "圆石", null, null, null};
int[] counts = {12, 32, 0, 0, 0};
java.util.Scanner input = new java.util.Scanner(System.in);
boolean running = true;
while (running) {
System.out.println("1查看 2添加 3删除 4查找 0退出");
if (!input.hasNextLine()) break;
String choice = input.nextLine().trim();
switch (choice) {
case "0" -> running = false;
case "1" -> show(items, counts);
case "2", "3" -> {
System.out.println("物品名:");
if (!input.hasNextLine()) { running = false; break; }InventoryManager.java · 第3/3段,顺序接在同一文件
String item = input.nextLine().trim();
System.out.println("正整数数量:");
if (!input.hasNextLine()) { running = false; break; }
int n = positive(input.nextLine());
boolean ok = choice.equals("2")
? add(items, counts, item, n)
: remove(items, counts, item, n);
System.out.println(ok ? "操作成功" : "操作拒绝,背包不变");
}
case "4" -> {
System.out.println("物品名:");
if (!input.hasNextLine()) { running = false; break; }
int slot = find(items, input.nextLine().trim());
System.out.println(slot < 0 ? "没找到" : "索引:" + slot);
}
default -> System.out.println("没有这个按钮");
}
}
System.out.println("背包已关闭");
}
}运行结果
1查看 2添加 3删除 4查找 0退出
物品名:
正整数数量:
操作成功
1查看 2添加 3删除 4查找 0退出
0: 木头 x17
1: 圆石 x32
2: 空位
3: 空位
4: 空位
1查看 2添加 3删除 4查找 0退出
背包已关闭15 / 16
拓展任务与阶段复盘
这是基础语法阶段的 挑战 关。提交源代码、运行结果、测试表和一条 Bug 记录。
- 木镐重现基本菜单
- 铁镐补齐测试与空位统计
- 钻石镐研究多个堆叠
- 复盘记录数据规则
三档作业
- 木镐:按完整菜单重现查看、添加、退出;提交输入与输出记录。
- 铁镐:完成删除与查找的边界测试,新增空格统计,确认错误输入不会改数据。
- 钻石镐:允许每种物品拥有多个堆叠,自动寻找未满64的格子。
| 自评 | 能 | 还要练 |
|---|---|---|
| 我能拆分项目方法 | □ | □ |
| 我能维护配对数组 | □ | □ |
| 我会写三类测试 | □ | □ |
16 / 16
自己的验收记录
运行之前写预期,运行之后记录实际结果。正常案例说明程序会做事;边界案例说明它知道何时停;错误案例说明它能拒绝不合理的请求。
| 类别 | 你选择的输入 | 预期与实际 |
|---|---|---|
| 正常 | 一个常用值或一次成功操作 | 是否逐行一致? |
| 边界 | 最小值、最大值及相邻值 | 有没有少一格、多一步? |
| 错误 | 非法数量、缺失对象或错误语法 | 是否明确拒绝?状态是否保持? |
- 把一个 Bug 写成:触发条件、现象、原因、最小修复。
- 整理一个值得记住的规则,用自己的话解释,不能只复制定义。
- 改变一个示例,保留测试记录,让结果证明你的理解。
TAKE IT WITH YOU
下载本课 PDF
完整课文、代码、图解与练习,选择你需要的语言。