← Java 基础教程

第 24 / 40 课 · 免费教程

坐标 方向与枚举

把合法方向和坐标规则写进类型

90–110 分钟本章使用 JDK 21,无需预览功能。面向对象进阶

本课作品

制作方块导航器 BlockNavigator

拼错一次方向,就可能让导航失灵。enum 把答案圈定,record 把坐标保存为值;移动返回新位置,边界由你明文规定。 前置背包:类、构造器、方法和接口。今天要给坐标装上防误改护甲,也要把拼错方向的入口关掉。

开始前:建议先完成第 19、21–23 课

任意文字都能混进方向变量
  1. 字符串可能拼错和大小写不一致
  2. 枚举类型圈定合法常量
  3. null仍需专门处理

01 / 12

开始前:先预测,再让程序回答

本章使用 JDK 21,无需预览功能。故事借用方块世界,示例运行在普通 Java 控制台;数值和规则若是课程模型,都会明确说明。

每个普通示例都是完整独立文件,按框上标注的文件名保存。各示例放在不同文件夹,避免辅助类重名。完整项目分段时,按顺序接在同一个文件中。

终端命令:中文占位符换成实际文件名与类名

javac -encoding UTF-8 示例文件名.java
java 示例类名
  • 预测:先写预期输出,或画清引用和数据的变化。
  • 验证:运行后逐行核对,再只改一个条件。
  • 解释:说清结果来自 Java 规则、课程合同还是输入数据。

02 / 12

字符串方向容易长出新品种

用 String 保存方向时,大小写、空格和拼写都可能制造非法状态。enum 把合法答案固定在一个有限集合里。

任意文字都能混进方向变量
  1. 字符串可能拼错和大小写不一致
  2. 枚举类型圈定合法常量
  3. null仍需专门处理

L24Example01.java · 完整独立示例

public class L24Example01 {
    public static void main(String[] args) {
        String facing = "dongg";
        System.out.println(facing);
        System.out.println(facing.equals("EAST"));
    }
}

运行结果

dongg
false

03 / 12

enum 圈出合法方向

枚举是一种类型。Direction facing 只能保存 Direction 中声明的值或 null。

枚举变量只能选择声明过的常量
  1. NORTH / SOUTH南北轴
  2. EAST / WEST东西轴
  3. UP / DOWN高度轴

L24Example02.java · 完整独立示例

enum Direction {
    NORTH(0, 0, -1), SOUTH(0, 0, 1),
    EAST(1, 0, 0), WEST(-1, 0, 0),
    UP(0, 1, 0), DOWN(0, -1, 0);
    final int dx, dy, dz;
    Direction(int dx, int dy, int dz) {
        this.dx = dx; this.dy = dy; this.dz = dz;
    }
}
public class L24Example02 {
    public static void main(String[] args) {
        Direction facing = Direction.EAST;
        System.out.println(facing);
        System.out.println(Direction.values().length);
    }
}

运行结果

EAST
6

BadDirection.java · 故意错误示例

enum Direction {
    NORTH(0, 0, -1), SOUTH(0, 0, 1),
    EAST(1, 0, 0), WEST(-1, 0, 0),
    UP(0, 1, 0), DOWN(0, -1, 0);
    final int dx, dy, dz;
    Direction(int dx, int dy, int dz) {
        this.dx = dx; this.dy = dy; this.dz = dz;
    }
}
public class BadDirection {
    public static void main(String[] args) { Direction d = Direction.DONGG; }
}

04 / 12

switch 覆盖方向分支

switch 直接匹配枚举常量,不需要 equals,也不会受拼写字符串影响。

枚举 switch 不再比较字符串
  1. switch覆盖全部六方向
  2. null 检查进入分支前处理
  3. 分支结果返回对应中文名

L24Example03.java · 完整独立示例

enum Direction {
    NORTH(0, 0, -1), SOUTH(0, 0, 1),
    EAST(1, 0, 0), WEST(-1, 0, 0),
    UP(0, 1, 0), DOWN(0, -1, 0);
    final int dx, dy, dz;
    Direction(int dx, int dy, int dz) {
        this.dx = dx; this.dy = dy; this.dz = dz;
    }
}
public class L24Example03 {
    static String label(Direction d) {
        if (d == null) return "未选择";
        return switch (d) {
            case NORTH -> "北"; case SOUTH -> "南";
            case EAST -> "东"; case WEST -> "西";
            case UP -> "上"; case DOWN -> "下";
        };
    }
    public static void main(String[] args) {
        System.out.println(label(Direction.NORTH));
        System.out.println(label(null));
    }
}

运行结果

北
未选择

05 / 12

枚举保存位移数据

枚举可以拥有字段和构造器。每个方向常量创建时保存自己的三轴增量。

六个方向与三轴位移
方向dxdydz
NORTH00-1
SOUTH001
EAST100
WEST-100
UP010
DOWN0-10

L24Example04.java · 完整独立示例

enum Direction {
    NORTH(0, 0, -1), SOUTH(0, 0, 1),
    EAST(1, 0, 0), WEST(-1, 0, 0),
    UP(0, 1, 0), DOWN(0, -1, 0);
    final int dx, dy, dz;
    Direction(int dx, int dy, int dz) {
        this.dx = dx; this.dy = dy; this.dz = dz;
    }
}
public class L24Example04 {
    public static void main(String[] args) {
        Direction d = Direction.NORTH;
        System.out.println(d.dx + "," + d.dy + "," + d.dz);
    }
}

运行结果

0,0,-1

06 / 12

record 装下三维坐标

坐标主要携带三个值,并按内容比较。record 会生成组件访问器和相等方法;当组件都是 int 等不可变值时,它适合表达不可变坐标。

一行声明生成构造器访问器和比较方法
  1. record 组件保存 x、y、z
  2. x() / y() / z()自动访问器
  3. equals按组件内容比较

L24Example05.java · 完整独立示例

record BlockPos(int x, int y, int z) { }
public class L24Example05 {
    public static void main(String[] args) {
        BlockPos p = new BlockPos(0, 64, 0);
        System.out.println(p.x());
        System.out.println(p.y());
        System.out.println(p.z());
    }
}

运行结果

0
64
0

07 / 12

移动返回新坐标

record 组件不能随意重新赋值。移动方法根据旧值计算并返回一个新 BlockPos。

旧位置保持不变 新位置表达结果
  1. start旧坐标保持不变
  2. move按方向计算增量
  3. next接收新的坐标对象

L24Example06.java · 完整独立示例

enum Direction {
    NORTH(0, 0, -1), SOUTH(0, 0, 1),
    EAST(1, 0, 0), WEST(-1, 0, 0),
    UP(0, 1, 0), DOWN(0, -1, 0);
    final int dx, dy, dz;
    Direction(int dx, int dy, int dz) {
        this.dx = dx; this.dy = dy; this.dz = dz;
    }
}
record BlockPos(int x, int y, int z) {
    BlockPos {
        if (y < -64 || y > 319)
            throw new IllegalArgumentException("高度应为-64到319");
    }
    BlockPos move(Direction direction, int steps) {
        if (direction == null || steps < 0) return this;
        long nx = (long) x + (long) direction.dx * steps;
        long ny = (long) y + (long) direction.dy * steps;
        long nz = (long) z + (long) direction.dz * steps;
        int nextY = (int) Math.max(-64L, Math.min(319L, ny));
        return new BlockPos(Math.toIntExact(nx), nextY,
                            Math.toIntExact(nz));
    }
    String shortText() { return "(" + x + "," + y + "," + z + ")"; }
}
public class L24Example06 {
    public static void main(String[] args) {
        BlockPos start = new BlockPos(0, 64, 0);
        BlockPos next = start.move(Direction.EAST, 5);
        System.out.println(start.shortText());
        System.out.println(next.shortText());
    }
}

运行结果

(0,64,0)
(5,64,0)

08 / 12

值对象的相等与边界

record 解决值的存储与比较,但不会自动知道 Minecraft 课程中的合法高度或移动规则。

内容相同的 record 自动 equals
  1. ==比较对象身份
  2. equals比较组件内容
  3. 高度范围最低-64,最高319

L24Example07.java · 完整独立示例

enum Direction {
    NORTH(0, 0, -1), SOUTH(0, 0, 1),
    EAST(1, 0, 0), WEST(-1, 0, 0),
    UP(0, 1, 0), DOWN(0, -1, 0);
    final int dx, dy, dz;
    Direction(int dx, int dy, int dz) {
        this.dx = dx; this.dy = dy; this.dz = dz;
    }
}
record BlockPos(int x, int y, int z) {
    BlockPos {
        if (y < -64 || y > 319)
            throw new IllegalArgumentException("高度应为-64到319");
    }
    BlockPos move(Direction direction, int steps) {
        if (direction == null || steps < 0) return this;
        long nx = (long) x + (long) direction.dx * steps;
        long ny = (long) y + (long) direction.dy * steps;
        long nz = (long) z + (long) direction.dz * steps;
        int nextY = (int) Math.max(-64L, Math.min(319L, ny));
        return new BlockPos(Math.toIntExact(nx), nextY,
                            Math.toIntExact(nz));
    }
    String shortText() { return "(" + x + "," + y + "," + z + ")"; }
}
public class L24Example07 {
    public static void main(String[] args) {
        BlockPos a = new BlockPos(0, 319, 0);
        BlockPos b = new BlockPos(0, 319, 0);
        System.out.println(a == b);
        System.out.println(a.equals(b));
        System.out.println(a.move(Direction.UP, 1).shortText());
        System.out.println(a.move(Direction.DOWN, 1000).shortText());
    }
}

运行结果

false
true
(0,319,0)
(0,-64,0)

09 / 12

挑战 蓝图方块导航器

挑战 程序叫 BlockNavigator。Direction 保存三轴位移,BlockPos 提供 move 与 shortText。

方向携带位移 坐标返回新值
  1. 方向与步数选定变化的轴
  2. 高度夹紧超过边界停在范围内
  3. 返回新值起点仍可回放

验收顺序

  • 从 (0,64,0) 出发,向东5格、向上300格、向北2格。
  • 高度夹紧后,终点应为 (5,319,-2)。
  • 向下1000格,结果应为 (5,-64,-2)。
  • 起点仍保持 (0,64,0)。
  • 负数步数和 null 方向返回原位置;两个同坐标对象 equals 为 true。

10 / 12

挑战 实现方块导航

新建 BlockNavigator.java,使用 Java 21 编译。

enum 与 record 组成不可变路径系统
  1. 起点0、64、0
  2. 终点5、319、-2
  3. 底部5、-64、-2

BlockNavigator.java · 第1/2段,顺序接在同一文件

enum Direction {
    NORTH(0, 0, -1), SOUTH(0, 0, 1),
    EAST(1, 0, 0), WEST(-1, 0, 0),
    UP(0, 1, 0), DOWN(0, -1, 0);
    final int dx, dy, dz;
    Direction(int dx, int dy, int dz) {
        this.dx = dx; this.dy = dy; this.dz = dz;
    }
}
record BlockPos(int x, int y, int z) {
    BlockPos {
        if (y < -64 || y > 319)
            throw new IllegalArgumentException("高度应为-64到319");
    }
    BlockPos move(Direction direction, int steps) {
        if (direction == null || steps < 0) return this;
        long nx = (long) x + (long) direction.dx * steps;
        long ny = (long) y + (long) direction.dy * steps;
        long nz = (long) z + (long) direction.dz * steps;
        int nextY = (int) Math.max(-64L, Math.min(319L, ny));
        return new BlockPos(Math.toIntExact(nx), nextY,
                            Math.toIntExact(nz));
    }
    String shortText() { return "(" + x + "," + y + "," + z + ")"; }
}
public class BlockNavigator {
    public static void main(String[] args) {
        BlockPos start = new BlockPos(0, 64, 0);

BlockNavigator.java · 第2/2段,顺序接在同一文件

        BlockPos next = start.move(Direction.EAST, 5)
            .move(Direction.UP, 300).move(Direction.NORTH, 2);
        System.out.println("起点=" + start.shortText());
        System.out.println("终点=" + next.shortText());
        System.out.println("底部=" + next.move(Direction.DOWN, 1000).shortText());
        System.out.println("内容相同=" + start.equals(new BlockPos(0, 64, 0)));
    }
}

运行结果

起点=(0,64,0)
终点=(5,319,-2)
底部=(5,-64,-2)
内容相同=true

11 / 12

拓展任务与通关复盘

作业要同时提交至少四个边界测试,并说明旧对象是否发生变化。

三档任务扩展不可变地图
  1. 边界案例顶层、底层及相邻值
  2. 非法输入负步数、空方向
  3. 对象检查旧值是否保持

三档作业

  • 木镐:创建 Difficulty 枚举,并用 switch 返回 EASY NORMAL HARD 的怪物倍率。
  • 铁镐:给 Direction 增加 dx dy dz,完成六个方向移动。
  • 钻石镐:设计 TeleportPoint record,保存名字和坐标,并返回偏移后的新传送点。
验收对照表
自评能还要练
我会声明 enum□□
我会使用 record□□
我理解不可变数据□□

12 / 12

自己的验收记录

漂亮的输出是起点,能解释边界才算掌握。把正常、边界与错误各选一个案例,运行前写预期,运行后留下证据。

为判断留下依据
类别你选择的操作核对重点
正常一个常用输入或一次成功动作结果是否逐行一致
边界范围端点与相邻值有没有多一步、少一项
错误非法数量、缺失对象或坏数据是否明确拒绝,状态是否保持
  • Bug 记录:触发条件、实际现象、原因、最小修复。
  • 图示记录:为每条箭头说明起点、终点和含义。
  • 改编记录:只改一个规则,同时修改预期和测试。

TAKE IT WITH YOU

下载本课 PDF

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