综合练习
用集合与 record 重构一个小模块。
40 分钟
综合练习
这一课你将能做到
- 独立实现「record DTO + enum 状态 + Set 去重 + Queue 发布队列 + Builder」的完整小模块,
java一条命令跑通 - 用 JUnit 5 写出覆盖重复 slug、FIFO 出队、非法参数的单元测试,并说出每条测试在保护什么
- 完成本 track 自检清单,把领域模型对接 Spring Boot 的下一步路线落到纸面
为什么要学
前 11 课的知识点都是「单点」,企业代码考验的是「组合」:一个课时目录模块同时用到 enum(状态)、record(DTO)、Map/Set(唯一性与顺序)、Queue(异步发布模拟)、Builder(构造)、异常(脏数据拦截)。自己从头敲一遍、再用测试锁住行为,才算把这些单点焊成能力。
这也是进入 Spring Boot 实战 前的最佳验收:本课的 TrackCatalog 在 sb-ioc 课会变成被容器托管的 Bean,DTO 会直接变成 REST 响应体——同一个模型,两种宿主。
先建立图像
模块结构(对应知识点标注):
TrackCatalogApp
├─ LessonStatus enum(第 2 课):DRAFT / PUBLISHED / ARCHIVED
├─ LessonView record + 紧凑构造器(第 6 课):不可变 DTO
├─ LessonBuilder 方法链(第 5 课):链式创建 + build 校验
└─ TrackCatalog
├─ Map<String,LessonView> slug 唯一索引(putIfAbsent,第 3/4 课)
├─ Queue<LessonView> 发布队列(ArrayDeque,FIFO)
├─ add() 重复 slug 返回 false
├─ enqueuePublish() 校验存在性与状态
└─ drainOne() 出队 → 状态流转 → 回写 Map
跟着做
步骤 1:实现完整模块(单文件可运行)
新建 TrackCatalogApp.java,完整可运行:
import java.util.ArrayDeque;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.HashSet;
public class TrackCatalogApp {
enum LessonStatus { DRAFT, PUBLISHED, ARCHIVED }
record LessonView(String slug, String title, int orderIndex, LessonStatus status) {
LessonView {
if (slug == null || slug.isBlank())
throw new IllegalArgumentException("slug 不能为空");
if (orderIndex < 0)
throw new IllegalArgumentException("orderIndex 不能为负");
}
LessonView publish() {
return new LessonView(slug, title, orderIndex, LessonStatus.PUBLISHED);
}
}
static class LessonBuilder {
private String slug;
private String title = "";
private int orderIndex;
private LessonStatus status = LessonStatus.DRAFT;
LessonBuilder slug(String v) { this.slug = v; return this; }
LessonBuilder title(String v) { this.title = v; return this; }
LessonBuilder order(int v) { this.orderIndex = v; return this; }
LessonBuilder status(LessonStatus v) { this.status = v; return this; }
LessonView build() {
return new LessonView(slug, title, orderIndex, status);
}
}
static class TrackCatalog {
private final Map<String, LessonView> lessons = new LinkedHashMap<>(); // 保留插入序
private final Queue<LessonView> publishQueue = new ArrayDeque<>();
/** slug 唯一:重复返回 false,不让脏数据进来 */
boolean add(LessonView lesson) {
return lessons.putIfAbsent(lesson.slug(), lesson) == null;
}
boolean enqueuePublish(String slug) {
LessonView lesson = lessons.get(slug);
if (lesson == null || lesson.status() != LessonStatus.DRAFT) return false;
return publishQueue.offer(lesson);
}
/** 模拟异步发布:出队 → 状态流转 → 回写目录 */
LessonView drainOne() {
LessonView queued = publishQueue.poll();
if (queued == null) return null;
LessonView published = queued.publish();
lessons.put(published.slug(), published);
return published;
}
int size() { return lessons.size(); }
Set<String> slugs() { return new HashSet<>(lessons.keySet()); }
}
public static void main(String[] args) {
TrackCatalog catalog = new TrackCatalog();
catalog.add(new LessonBuilder().slug("jva-intro").title("Java 进阶概览").order(1).build());
catalog.add(new LessonBuilder().slug("jva-enums").title("枚举与模式").order(2).build());
boolean dup = catalog.add(new LessonBuilder().slug("jva-intro").title("重复").order(9).build());
System.out.println("重复 slug 添加结果:" + dup); // false
catalog.enqueuePublish("jva-intro");
catalog.enqueuePublish("jva-enums");
catalog.enqueuePublish("不存在"); // 静默失败,返回 false
LessonView published;
while ((published = catalog.drainOne()) != null) {
System.out.println("已发布:" + published.slug() + " -> " + published.status());
}
System.out.println("目录大小:" + catalog.size() + ",slug 集合:" + catalog.slugs());
}
}