综合练习与 dsa-java 分工
小项目、Spring Boot 下一步。
综合练习与 dsa-java 分工
这一课你将能做到
- 独立完成一个 200 行以内的「用户管理系统」控制台项目,串起类型、集合、OOP、异常、IO 五大知识块
- 对照检查清单自评 13 课掌握度,明确薄弱项回去补哪一课
- 说清本课程与 dsa-java(算法路线)的分工边界,知道下一步进 Spring Boot 路线该带什么行囊
为什么要学
前面的课都是「单点技能」:一个知识点一个 Demo。真实工程是技能的组合:一个方法里同时有类型判断、集合操作、异常处理、对象设计。单点会 ≠ 组合会——中间隔着「面对需求不知道从哪下手」这道坎。
这一课用一个小项目打通任督二脉。场景选「用户管理」,因为它是本站后端的真实缩影:有实体(User)、有存储(Map 内存模拟 → 文件持久化模拟 MySQL)、有校验(异常)、有增删改查(Service 层逻辑)。
同时划清一个重要边界:语法课不负责刷题。链表反转、二叉树遍历、动态规划,请去 dsa-java 路线——两边目标不同,混着学两头空。
先建立图像
项目的分层雏形(Spring 后端三层架构的控制台极简版):
main(入口/菜单循环) ← 相当于 Controller:接收指令、分发
│
UserManager(业务层) ← 相当于 Service:增删改查规则
│
Map<Long, User>(内存存储) ← 相当于数据库
│
UserFileStore(持久层) ← 相当于 MySQL:读写 kv-store.txt 落盘
退出前落盘、启动时加载 → 程序重启数据不丢(这就是「持久化」的直觉)
跟着做
步骤 1:定义实体与业务异常
新建 UserApp.java(单文件多类,java UserApp.java 直跑):
import java.io.IOException;
import java.nio.file.*;
import java.util.*;
// 实体:不可变属性少、可变状态多,用传统类;字段校验走 setter
class User {
private long id;
private String name;
private int age;
public User(long id, String name, int age) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("用户名不能为空");
}
this.id = id;
this.name = name;
setAge(age);
}
public long getId() { return id; }
public String getName() { return name; }
public int getAge() { return age; }
public void setAge(int age) {
if (age < 0 || age > 150) throw new IllegalArgumentException("年龄非法: " + age);
this.age = age;
}
@Override
public String toString() {
return "User{id=%d, name=%s, age=%d}".formatted(id, name, age);
}
}
// 业务异常:继承 RuntimeException,带错误码(呼应第 11 课)
class UserNotFoundException extends RuntimeException {
public UserNotFoundException(long id) {
super("用户不存在: id=" + id);
}
}
class DuplicateUserException extends RuntimeException {
public DuplicateUserException(String name) {
super("用户名已存在: " + name);
}
}
步骤 2:持久层——文件存储
// 继续加到 UserApp.java 里
class UserFileStore {
private final Path file = Path.of("users.txt");
// 格式约定:每行 id,name,age(简易 CSV)
void save(Map<Long, User> users) {
StringBuilder sb = new StringBuilder();
users.values().forEach(u ->
sb.append(u.getId()).append(",")
.append(u.getName()).append(",")
.append(u.getAge()).append("\n"));
try {
Files.writeString(file, sb.toString());
} catch (IOException e) {
throw new RuntimeException("落盘失败", e); // 包装受检异常为非受检上抛
}
}
Map<Long, User> load() {
Map<Long, User> users = new HashMap<>();
if (Files.notExists(file)) return users;
try (var lines = Files.lines(file)) {
lines.forEach(line -> {
if (line.isBlank()) return; // 跳过空行
String[] p = line.split(",");
users.put(Long.parseLong(p[0]),
new User(Long.parseLong(p[0]), p[1], Integer.parseInt(p[2])));
});
} catch (IOException e) {
throw new RuntimeException("加载失败", e);
}
return users;
}
}