Java初学者项目实战:创建图书管理系统

先用纯内存实现核心逻辑,再加UI和数据库;Book类必须重写equals()和hashCode()且仅比较final id;用Map替代List提升查找效率;借阅状态应封装为BorrowRecord解耦;命令行交互采用Command接口+命令映射表实现可扩展性;所有用户输入须做非空与长度校验。

Java初学者做图书管理系统,别一上来就画界面、连数据库——90%的人卡在类设计混乱和对象关系没理清上,导致后期增删改查处处报 NullPointerException 或数据不一致。先用纯内存实现核心逻辑,跑通再加 UI 和持久化。

图书类(Book)必须重写 equals()hashCode()

否则用 ArrayList.remove(book)HashSet 去重时永远删不掉、查不到。很多初学者只靠 id 判断相等,但没覆盖方法,结果比较的是内存地址。

实操建议:

  • id 字段设为 final,避免后期被意外修改
  • equals() 只比 id(假设 ID 唯一),不要比书名或作者——同名书可能有多个版本
  • 用 IDE 自动生成(如 IntelliJ 的 Alt+Insert → equals and hashCode),别手写漏字段
public class Book {
    private final String id;
    private final String title;
    private final String author;

    // 构造、getter 略

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Book book = (Book) o;
        return Objects.equals(id, book.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}

Map 而不是 List 存图书集合

按 ID 查找是高频操作(借阅、归还、修改),List 遍历查找时间复杂度 O(n),HashMap 是 O(1)。初学者常因“学过 List 就用 List”,结果搜索慢、代码臃肿。

使用场景:

  • 添加:用 books.put(book.getId(), book)
  • 查找:直接 books.get("B001"),不用 for 循环
  • 删除:用 books.remove("B001"),安全且高效
  • 遍历所有书:用 books.values() 获取 Collection 视图

借阅状态不能只靠布尔字段,要封装成 BorrowRecord

很多作业里用 book.setBorrowed(true),看似简单,但立刻暴露问题:谁借的?什么时候借的?有没有逾期?后续加统计功能就全得推翻重写。

为什么这样做:

  • 把“借”这个动作从 Book 解耦出来,符合单一职责
  • 未来可轻松扩展字段:borrowDatereturnDatereaderId
  • 避免 Book 类膨胀成“借阅管理器”

示例结构:

public class BorrowRecord {
    private final String bookId;
    private final String readerId;
    private final LocalDateTime borrowTime;

    // 构造 + getter 略
}

系统里单独维护 Map(key 为 bookId),借出时存入,归还时 remove

命令行交互别写死 Scanner.nextLine() 堆砌

初学者容易把主循环写成几十行嵌套 if-else,输入一个数字就跳一个分支,结果加个新功能就得全局改。真正该做的是用“命令分发”思路。

实操建议:

  • 定义统一命令接口:interface Command { void execute(); }
  • 每个功能(如 AddBookCommandFindBookCommand)实现它
  • Map 维护命令映射,输入 "add" 就执行对应实例
  • 输入解析单独抽成 CommandParser.parse(input),后续支持命令参数(如 find B001)也容易扩展

这样哪怕加到 20 个功能,主循环还是 5 行以内,不会变成意大利面条代码。

最易被忽略的一点:所有用户输入的 id、书名、作者,必须做非空和长度校验——不是为了“健壮性”这种虚词,而是防止后面 Map.get(null) 直接抛异常,或者空字符串作为 key 导致多本书互相覆盖。校验代码三行就能写完,但缺了它,调试时间翻倍。