← Java 基础教程

第 16 / 40 课 · 免费教程

阶段项目:背包管理员

综合基础语法项目

110–120 分钟本章使用 JDK 21,无需预览功能。基础综合

本课作品

制作可交互的背包管理员

会把变量 条件 循环 方法和数组装进一个可运行项目;能设计菜单循环和安全输入;能完成查看 添加 删除 查找四项功能;能用测试表验收背包管理员。 前置背包:第1至15课的变量、输入、条件、循环、方法与数组。今天不是再学一块新砖,而是把旧砖盖成能住人的程序。

开始前:建议先完成第 1–15 课

两组数组,一份背包记录
格子物品数量
0木头12
1圆石32
2null0

01 / 16

开始前:把示例放对地方

本章使用 JDK 21,不需要预览功能。每个普通示例都给出完整程序,按标注的文件名保存。多个示例各放在自己的文件夹,避免辅助类同名。

终端命令:把中文占位符替换成实际名称

javac -encoding UTF-8 示例文件名.java
java 示例类名

类名区分大小写。完整挑战代码若分成多段,请按顺序接在同一个文件中,不要为续段另建类。运行核对只列程序输出;需要输入的地方另给输入序列。

02 / 16

项目地图与验收条件

前十五课的知识今天组队打 挑战。挑战 不是末影龙,而是一个会不停问“请选择菜单”的控制台程序。

最终程序能查看、添加、删除、查找物品,并且遇到错误输入不会原地爆炸。

背包管理员先画地图再开工
  1. 菜单重复接收操作
  2. 配对数组同格保存名字与数量
  3. 规则方法查看、添加、删除、查找
  4. 验收正常、边界、错误三类

完成标准

  • 菜单会重复显示,选择0才退出。
  • 物品名和数量使用相同索引保存。
  • 添加和删除都检查数量与背包边界。
  • 至少准备正常、边界、错误三类测试。

03 / 16

背包的数据模型

一个格子要保存物品名和数量。当前阶段还没学习自定义物品类,所以先用两个等长数组配对。

同一索引是一对数据
索引items[i]counts[i]
0木头12
1圆石32
2null0

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]);
    }
}

运行结果

圆石 x32

04 / 16

菜单循环控制程序

菜单型程序通常不知道玩家要操作几次,所以使用 while。每轮显示菜单、读取选择、执行功能,再回到入口。

菜单循环:0 才是出口
步骤操作
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 先得到文本,再尝试转换为整数。输入“钻石”时,程序应给提示,而不是用异常把玩家踢出世界。

输入先过检查站再进入背包
  1. 文本读取并去除边缘空白
  2. parseInt尝试转成int
  3. 正整数成功且大于0才接受
  4. 非法-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
-1

06 / 16

查看背包

查看功能需要打印索引,所以普通 for 比增强 for 更合适。空格统一显示为“空位”。

普通 for 同时拿到格子编号和内容
  1. 有物品打印名字与数量
  2. 空位统一显示空位
  3. 索引显示0到length减1
  4. 数组必须等长且配对合法

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。

搜索循环遇到目标就返回索引
  1. target先排除null
  2. equals按文字内容比较
  3. 找到返回首个索引
  4. 未找到返回-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
-1

08 / 16

添加与堆叠

添加物品有两条路线:背包已有该物品时增加数量;没有时放进第一个空格。每格最多64个。

先找同类物品 再找空格
  1. 已存在使用已有格子
  2. 新物品找第一个空格
  3. 余量先检查64减当前数量
  4. 成功同步写入名字和数量

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
17

09 / 16

删除与清空格子

删除方法先找到物品,再确认请求数量合法。减到0时必须同时清空物品名。

数量归零时名字和数量一起清空
  1. 查找未找到就拒绝
  2. 数量大于0且不超库存
  3. 扣除检查通过后再修改
  4. 清空归零时名字也变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:0

10 / 16

方法分工

main 负责菜单与流程,功能方法负责数据规则。这样出错时能快速判断是哪一位“村民”交错了货。

main 当调度员 方法当专业村民
  1. 显示show负责输出
  2. 查找find返回索引
  3. 修改add和remove维护规则
  4. 输入main读取并安排操作
验收表
方法输入返回或效果
show两个数组打印每格状态
find物品数组、名称索引或-1
add数组、名称、数量成功或失败
remove数组、名称、数量成功或失败
positive一行文本正整数或-1

11 / 16

三类测试

项目不是“能运行一次”就通关。每项功能至少准备一个正常测试、一个边界测试和一个错误测试。

测试台照出正常 边界和错误
  1. 正常功能能完成
  2. 边界恰满、恰空
  3. 错误非法数量与未知命令
  4. 回归修复后重跑原测试
验收表
功能输入预期
添加木头 5数量增加5
添加圆石 40 到已有32拒绝 超过64
删除木头 0拒绝 数据不变
查找不存在的钻石返回-1
菜单x提示没有按钮

12 / 16

项目故障诊所

项目变大后,不要凭感觉乱改。先记录输入和现象,再定位负责该规则的方法。

从现象追到负责的方法
  1. 死循环退出未改变条件
  2. 配对错名字与数量不同步
  3. 越界索引不合法
  4. 超量检查失败必须不改数据

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]);
    }
}

运行结果

木头 x0

13 / 16

挑战 蓝图

挑战 程序叫 InventoryManager。完整版本包含可重复操作的控制台菜单,查看、添加、删除、查找都有真实实现。先完成数据方法,再接入菜单。

背包管理员的四个核心功能
  1. 先实现查看与查找
  2. 再实现添加与删除
  3. 接菜单命令调用真实功能
  4. 验收失败时两个数组不变

验收顺序

  • 显示初始背包。
  • 添加5个木头并确认数量。
  • 加入3个钻石并确认占用空格。
  • 删除全部圆石并确认格子清空。
  • 尝试添加导致超过64的数量,必须失败。
验收表
规则合法范围失败表现
索引0到length-1不访问数组
数量大于0返回false
堆叠最多64数据不变
空格至少一个null返回false

14 / 16

挑战 实现

新建 InventoryManager.java。程序不依赖真实 Minecraft 接口,可直接在 Java 21 控制台编译运行。

完整程序把五种基础语法连成一条链
  1. 基础数组与查找
  2. 规则余量检查与清空
  3. 菜单while与switch
  4. 输入异常处理与结束检查

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 记录。

三档扩建让背包真正像项目
  1. 木镐重现基本菜单
  2. 铁镐补齐测试与空位统计
  3. 钻石镐研究多个堆叠
  4. 复盘记录数据规则

三档作业

  • 木镐:按完整菜单重现查看、添加、退出;提交输入与输出记录。
  • 铁镐:完成删除与查找的边界测试,新增空格统计,确认错误输入不会改数据。
  • 钻石镐:允许每种物品拥有多个堆叠,自动寻找未满64的格子。
验收表
自评能还要练
我能拆分项目方法□□
我能维护配对数组□□
我会写三类测试□□

16 / 16

自己的验收记录

运行之前写预期,运行之后记录实际结果。正常案例说明程序会做事;边界案例说明它知道何时停;错误案例说明它能拒绝不合理的请求。

把证据写进记录
类别你选择的输入预期与实际
正常一个常用值或一次成功操作是否逐行一致?
边界最小值、最大值及相邻值有没有少一格、多一步?
错误非法数量、缺失对象或错误语法是否明确拒绝?状态是否保持?
  • 把一个 Bug 写成:触发条件、现象、原因、最小修复。
  • 整理一个值得记住的规则,用自己的话解释,不能只复制定义。
  • 改变一个示例,保留测试记录,让结果证明你的理解。

TAKE IT WITH YOU

下载本课 PDF

完整课文、代码、图解与练习,选择你需要的语言。