项目案例作业(选做):使用文件改造已有信息系统

题目要求

改造学生管理系统或购物车系统,添加基于文本文件与基于二进制文件的存储系统。

这里我是以之前学生信息管理系统博客里的代码为基础进行的改造。

改动点

(1)创建 BinaryFileStudentDAO.java 文件

层级结构如下:

代码如下:

java 复制代码
package dao.impl;

import dao.StudentDAO;
import entity.Student;
import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class BinaryFileStudentDAO implements StudentDAO {
    private static final String FILE_PATH = "students.dat"; // 二进制存储文件

    @Override
    public void addStudent(Student student) {
        List<Student> students = getStudents();
        students.add(student);
        saveStudents(students);
    }

    @Override
    public boolean removeStudent(String id) {
        List<Student> students = getStudents();
        boolean removed = students.removeIf(student -> student.getId().equals(id));
        if (removed) saveStudents(students);
        return removed;
    }

    @Override
    public List<Student> getStudents() {
        File file = new File(FILE_PATH);
        if (!file.exists()) return new ArrayList<>();

        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
            return (List<Student>) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
            return new ArrayList<>();
        }
    }

    // 序列化保存学生列表
    private void saveStudents(List<Student> students) {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_PATH))) {
            oos.writeObject(students);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public List<Student> searchByName(String name) {
        List<Student> result = new ArrayList<>();
        for (Student s : getStudents()) {
            if (s.getName().contains(name)) result.add(s);
        }
        return result;
    }

    @Override
    public List<Student> searchByMajor(String major) {
        List<Student> result = new ArrayList<>();
        for (Student s : getStudents()) {
            if (s.getMajor().contains(major)) result.add(s);
        }
        return result;
    }

    @Override
    public List<Student> searchByGpa(double gpa) {
        List<Student> result = new ArrayList<>();
        for (Student s : getStudents()) {
            if (Math.abs(s.getGpa() - gpa) < 0.001) result.add(s);
        }
        return result;
    }
}

(2)修改 Student.java 文件

主要是新增这一条语句:

java 复制代码
private static final long serialVersionUID = 1L; 

完整代码如下:

java 复制代码
package entity;

import java.io.Serializable; // 导入序列化接口

public class Student implements Serializable { // 实现接口
    private static final long serialVersionUID = 1L; // 序列化版本号(新增)
    private String id;
    private String name;
    private String major;
    private double gpa;
    private int age;
    private String gender;

    public Student(String id, String name, String major, double gpa, int age, String gender) {
        this.id = id;
        this.name = name;
        this.major = major;
        this.gpa = gpa;
        this.age = age;
        this.gender = gender;
    }

    public Student() {}

    public Student(String id, String name, String major, double gpa) {
        this(id, name, major, gpa, 0, "未知");
    }

    @Override
    public String toString() {
        return "学号:" + id + ",姓名:" + name + ",专业:" + major + ",GPA:" + gpa + ",年龄:" + age + ",性别:" + gender;
    }

    // 所有getter/setter保持不变
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getMajor() { return major; }
    public void setMajor(String major) { this.major = major; }
    public double getGpa() { return gpa; }
    public void setGpa(double gpa) { this.gpa = gpa; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
    public String getGender() { return gender; }
    public void setGender(String gender) { this.gender = gender; }
}

(3)修改 Main.java 文件

主要是把二进制的 DAO 文件导入。

代码如下:

java 复制代码
package code;

import dao.StudentDAO;
import dao.impl.ListStudentDAO;
import dao.impl.TextFileStudentDAO;
import dao.impl.ExcelStudentDAO;
import dao.impl.BinaryFileStudentDAO; // 导入二进制DAO
import entity.Student;
import service.StudentService;
import java.util.List;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        StudentService service = null;
        System.out.println("选择存储模式:1. List 存储  2. 文本文件存储  3. Excel 存储  4. 二进制文件存储");
        int choice = scanner.nextInt();
        scanner.nextLine();

        if (choice == 1) {
            StudentDAO dao = new ListStudentDAO();
            service = new StudentService(dao);
        } else if (choice == 2) {
            StudentDAO dao = new TextFileStudentDAO();
            service = new StudentService(dao);
        } else if (choice == 3) {
            StudentDAO dao = new ExcelStudentDAO();
            service = new StudentService(dao);
        } else if (choice == 4) {
            StudentDAO dao = new BinaryFileStudentDAO();
            service = new StudentService(dao);
        } else {
            System.out.println("无效选择,退出程序");
            return;
        }

        boolean running = true;
        while (running) {
            System.out.println("\n学生信息管理系统");
            System.out.println("1. 添加学生");
            System.out.println("2. 删除学生");
            System.out.println("3. 查看所有学生");
            System.out.println("4. 按姓名搜索学生");
            System.out.println("5. 按专业搜索学生");
            System.out.println("6. 按 GPA 搜索学生");
            System.out.println("7. 退出");
            System.out.print("请选择操作:");

            int operation = scanner.nextInt();
            scanner.nextLine();

            switch (operation) {
                case 1:
                    Student student = new Student();
                    System.out.print("输入学号:");
                    student.setId(scanner.nextLine());
                    System.out.print("输入姓名:");
                    student.setName(scanner.nextLine());
                    System.out.print("输入专业:");
                    student.setMajor(scanner.nextLine());
                    System.out.print("输入 GPA:");
                    student.setGpa(scanner.nextDouble());
                    scanner.nextLine(); // 吸收换行符
                    System.out.print("输入年龄:");
                    student.setAge(scanner.nextInt());
                    scanner.nextLine(); // 吸收换行符
                    System.out.print("输入性别:");
                    student.setGender(scanner.nextLine());

                    service.addStudent(student);
                    System.out.println("学生添加成功");
                    break;
                case 2:
                    System.out.print("输入要删除的学生学号:");
                    String id = scanner.nextLine();
                    if (service.removeStudent(id)) {
                        System.out.println("学生删除成功");
                    } else {
                        System.out.println("未找到该学号的学生");
                    }
                    break;
                case 3:
                    List<Student> allStudents = service.getStudents();
                    if (allStudents.isEmpty()) {
                        System.out.println("暂无学生信息");
                    } else {
                        for (Student s : allStudents) {
                            System.out.println(s);
                        }
                    }
                    break;
                case 4:
                    System.out.print("输入要搜索的学生姓名:");
                    String name = scanner.nextLine();
                    List<Student> nameStudents = service.searchByName(name);
                    if (nameStudents.isEmpty()) {
                        System.out.println("未找到该姓名的学生");
                    } else {
                        for (Student s : nameStudents) {
                            System.out.println(s);
                        }
                    }
                    break;
                case 5:
                    System.out.print("输入要搜索的专业:");
                    String major = scanner.nextLine();
                    List<Student> majorStudents = service.searchByMajor(major);
                    if (majorStudents.isEmpty()) {
                        System.out.println("未找到该专业的学生");
                    } else {
                        for (Student s : majorStudents) {
                            System.out.println(s);
                        }
                    }
                    break;
                case 6:
                    System.out.print("输入要搜索的 GPA:");
                    double gpa = scanner.nextDouble();
                    scanner.nextLine();
                    List<Student> gpaStudents = service.searchByGpa(gpa);
                    if (gpaStudents.isEmpty()) {
                        System.out.println("未找到该 GPA 的学生");
                    } else {
                        for (Student s : gpaStudents) {
                            System.out.println(s);
                        }
                    }
                    break;
                case 7:
                    running = false;
                    System.out.println("程序已退出");
                    break;
                default:
                    System.out.println("无效操作,请重新选择");
            }
        }
        scanner.close();
    }
}

运行结果

大家会发现我们输出的 student.dat 文件出现了乱码,这是因为 students.dat 是二进制序列化文件,并非文本文件,用文本编辑器会把二进制数据按照字符编码(如 UTF-8)去解析,而序列化的二进制数据并不是合法的文本字符,因此会显示为乱码。

那如何解决这个问题呢?这里我们推荐一个软件 notepad-- ,我们用它来打开我们的 student.dat 文件,再点击菜单-编辑-以二进制模式打开:

这下就能看到清晰的十六进制字符串了。

问题回答

能否理解 Student 类中需要加入 serialVersionUID 属性?

首先说说 serialVersionUID 的作用:

serialVersionUID是序列化机制用于版本控制的标识,它的主要作用是:

  1. 保证版本一致性:当类实现 Serializable 接口且未显式定义 serialVersionUID 时,Java 会自动生成一个(基于类的字段、方法等结构)。
  2. 避免反序列化异常:如果序列化后类结构发生变化(如增减字段、修改方法),自动生成的 serialVersionUID 会改变。此时用旧版本字节流反序列化新版本类,会抛出 InvalidClassException。
  3. 支持兼容性修改:显式定义 serialVersionUID 后,即使类结构有小变动(如新增字段),只要版本号不变,反序列化仍能兼容旧版本字节流。

那为什么 Student 类需要它呢?

因为在 BinaryFileStudentDAO 中,通过对象序列化读写学生数据:

java 复制代码
// BinaryFileStudentDAO中序列化学生列表
private void saveStudents(List<Student> students) {
    try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_PATH))) {
        oos.writeObject(students);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

这段代码中,students 列表被序列化到文件,而列表中的元素 Student 必须实现 Serializable 接口。此时如果不显式定义 serialVersionUID,当 Student 类结构变化时,自动生成的 serialVersionUID 会改变,导致旧文件中的数据无法反序列化。而显式定义后,只要版本号不变,就能保证新旧版本的兼容性。

今天的分享就到这里。

相关推荐
地平线开发者8 小时前
J6B vio scenario sample
算法
Flittly15 小时前
【AgentScope Java新手村系列】(16)从RAG到多路检索
java·spring boot·spring
小兔崽子去哪了16 小时前
Java 生成二维码解决方案
java·后端
BothSavage20 小时前
Trae远程开发中DeepSeek自定义模型4054错误的排查与修复
算法
小林ixn20 小时前
从暴力到KMP:一道题彻底搞懂字符串匹配的前世今生
算法
人活一口气20 小时前
从JVM调优到MCP协议:Java全栈技术体系深度总结与企业级架构实践
java·spring boot
烬羽1 天前
字符串算法入门:从反转字符串到回文判断,面试不再慌
算法·面试
NE_STOP1 天前
Vibe Coding -- 完整项目案例实操
java
荣码1 天前
GraphRAG:普通RAG只能回答"点"的问题,我踩了4个坑才搞懂
java·python