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

题目要求

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

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

改动点

(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 会改变,导致旧文件中的数据无法反序列化。而显式定义后,只要版本号不变,就能保证新旧版本的兼容性。

今天的分享就到这里。

相关推荐
~无忧花开~3 小时前
JavaScript实现PDF本地预览技巧
开发语言·前端·javascript
靠沿3 小时前
Java数据结构初阶——LinkedList
java·开发语言·数据结构
4***99743 小时前
Kotlin序列处理
android·开发语言·kotlin
froginwe113 小时前
Scala 提取器(Extractor)
开发语言
t***D2643 小时前
Kotlin在服务端开发中的生态建设
android·开发语言·kotlin
qq_12498707533 小时前
基于springboot的建筑业数据管理系统的设计与实现(源码+论文+部署+安装)
java·spring boot·后端·毕业设计
一 乐3 小时前
宠物管理|宠物共享|基于Java+vue的宠物共享管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·springboot·宠物
a crazy day3 小时前
Spring相关知识点【详细版】
java·spring·rpc
Elias不吃糖3 小时前
LeetCode每日一练(209, 167)
数据结构·c++·算法·leetcode