跳转至

实战项目 - 学生管理系统

📖 项目简介

本章将通过构建一个完整的学生管理系统,综合运用前面学到的Java知识。

🎯 项目目标

完成本项目后,你将能够: - 构建完整的Java控制台应用 - 实现学生信息的增删改查 - 掌握面向对象编程的实际应用 - 理解集合框架的使用 - 掌握文件IO操作

学生管理系统分层架构图

🏗️ 项目结构

Text Only
StudentManagementSystem/
├── src/
│   ├── main/
│   │   ├── model/
│   │   │   ├── Student.java
│   │   │   └── Course.java
│   │   ├── service/
│   │   │   ├── StudentService.java
│   │   │   └── CourseService.java
│   │   ├── dao/
│   │   │   ├── StudentDAO.java
│   │   │   └── CourseDAO.java
│   │   ├── util/
│   │   │   ├── FileUtil.java
│   │   │   └── InputUtil.java
│   │   └── Main.java
├── data/
│   └── students.dat
└── README.md

📄 核心功能实现

1. 学生模型

Java
// Student.java — 学生实体模型类
package main.model;

import java.io.Serializable;

// 实现 Serializable 接口,使学生对象可以通过 ObjectOutputStream 序列化到文件
public class Student implements Serializable {
    // 序列化版本号,用于反序列化时验证类的兼容性
    private static final long serialVersionUID = 1L;

    // 学生属性字段 —— 使用 private 封装,通过 Getter/Setter 访问(JavaBean 规范)
    private String id;         // 学号(唯一标识)
    private String name;       // 姓名
    private int age;           // 年龄
    private String gender;     // 性别
    private String className;  // 班级
    private double score;      // 成绩

    // 无参构造器 —— 反序列化和框架实例化时需要
    public Student() {
    }

    // 全参构造器 —— 创建新学生时使用
    public Student(String id, String name, int age, String gender, String className, double score) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.className = className;
        this.score = score;
    }

    // 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 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;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }

    // 重写 toString —— 使用 String.format 格式化输出学生信息,方便打印和调试
    @Override
    public String toString() {
        return String.format("学号: %s, 姓名: %s, 年龄: %d, 性别: %s, 班级: %s, 成绩: %.1f",
                id, name, age, gender, className, score);
    }
}

2. 学生DAO

Java
// StudentDAO.java — 数据访问层(DAO 模式)
// 负责学生数据的增删改查操作,将数据操作与业务逻辑分离
package main.dao;

import main.model.Student;
import java.util.ArrayList;
import java.util.List;

public class StudentDAO {
    // 使用 ArrayList 作为内存数据存储(生产环境应替换为数据库)
    private List<Student> students;

    public StudentDAO() {
        students = new ArrayList<>();
    }

    // 添加学生 —— 直接追加到列表末尾,时间复杂度 O(1)
    public void addStudent(Student student) {
        students.add(student);
        System.out.println("学生添加成功: " + student.getName());
    }

    // 删除学生 —— 使用 Lambda 表达式 + removeIf 简化条件删除
    // removeIf 内部遍历列表,移除所有满足条件的元素
    public boolean deleteStudent(String id) {
        boolean removed = students.removeIf(s -> s.getId().equals(id));
        if (removed) {
            System.out.println("学生删除成功: " + id);
        } else {
            System.out.println("学生不存在: " + id);
        }
        return removed;
    }

    // 更新学生 —— 通过学号定位,使用 set() 替换整个对象
    public boolean updateStudent(Student updatedStudent) {
        for (int i = 0; i < students.size(); i++) {
            // 按学号匹配(equals 比较字符串内容,而非引用地址)
            if (students.get(i).getId().equals(updatedStudent.getId())) {
                students.set(i, updatedStudent);
                System.out.println("学生更新成功: " + updatedStudent.getName());
                return true;
            }
        }
        System.out.println("学生不存在: " + updatedStudent.getId());
        return false;
    }

    // 查询所有学生 —— 返回内部列表的引用(注意:调用方可修改原数据)
    public List<Student> getAllStudents() {
        return students;
    }

    // 根据ID精确查询 —— 遍历查找,时间复杂度 O(n)
    // 生产环境建议使用 HashMap<String, Student> 优化为 O(1)
    public Student getStudentById(String id) {
        for (Student student : students) {
            if (student.getId().equals(id)) {
                return student;
            }
        }
        return null; // 未找到返回 null
    }

    // 根据姓名模糊查询 —— contains() 实现模糊匹配(包含关系)
    public List<Student> getStudentsByName(String name) {
        List<Student> result = new ArrayList<>();
        for (Student student : students) {
            if (student.getName().contains(name)) {
                result.add(student);
            }
        }
        return result;
    }

    // 统计学生数量 —— 直接返回集合 size
    public int getStudentCount() {
        return students.size();
    }

    // 计算全体学生平均成绩
    // 注意先检查空集合,避免除零异常
    public double getAverageScore() {
        if (students.isEmpty()) {
            return 0;
        }

        // 累加所有学生成绩(也可用 Stream: students.stream().mapToDouble(Student::getScore).average())
        double total = 0;
        for (Student student : students) {
            total += student.getScore();
        }
        return total / students.size();
    }
}

3. 学生服务

Java
// StudentService.java — 业务逻辑层(Service 层)
// 负责协调 DAO 层和用户交互,实现菜单驱动的业务流程
package main.service;

import main.dao.StudentDAO;
import main.model.Student;
import main.util.FileUtil;
import main.util.InputUtil;
import java.util.List;
import java.util.Scanner;

public class StudentService {
    private StudentDAO studentDAO;  // 数据访问对象
    private Scanner scanner;        // 控制台输入扫描器

    public StudentService() {
        studentDAO = new StudentDAO();
        scanner = new Scanner(System.in);
    }

    // 显示主菜单 —— 使用 while(true) + switch 实现菜单循环
    // 用户选择 0 时 return 退出循环
    public void showMainMenu() {
        while (true) {
            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. 统计信息");
            System.out.println("7. 保存数据");
            System.out.println("8. 加载数据");
            System.out.println("0. 退出");
            System.out.print("请选择操作: ");

            int choice = InputUtil.getIntInput("请选择操作", 0, 8);

            switch (choice) {
                case 1:
                    addStudent();
                    break;
                case 2:
                    showAllStudents();
                    break;
                case 3:
                    searchStudent();
                    break;
                case 4:
                    updateStudent();
                    break;
                case 5:
                    deleteStudent();
                    break;
                case 6:
                    showStatistics();
                    break;
                case 7:
                    saveData();
                    break;
                case 8:
                    loadData();
                    break;
                case 0:
                    System.out.println("感谢使用学生管理系统!");
                    return;
                default:
                    System.out.println("无效的选择!");
            }
        }
    }

    // 添加学生
    private void addStudent() {
        System.out.println("\n--- 添加学生 ---");
        String id = InputUtil.getStringInput("学号");
        String name = InputUtil.getStringInput("姓名");
        int age = InputUtil.getIntInput("年龄", 1, 120);
        String gender = InputUtil.getStringInput("性别");
        String className = InputUtil.getStringInput("班级");
        double score = InputUtil.getDoubleInput("成绩", 0, 100);

        Student student = new Student(id, name, age, gender, className, score);
        studentDAO.addStudent(student);
    }

    // 显示所有学生
    private void showAllStudents() {
        System.out.println("\n--- 所有学生 ---");
        List<Student> students = studentDAO.getAllStudents();

        if (students.isEmpty()) {
            System.out.println("暂无学生信息");
            return;
        }

        System.out.println("学号\t姓名\t年龄\t性别\t班级\t成绩");
        System.out.println("----------------------------------------");
        for (Student student : students) {
            System.out.printf("%s\t%s\t%d\t%s\t%s\t%.1f%n",
                    student.getId(), student.getName(), student.getAge(),
                    student.getGender(), student.getClassName(), student.getScore());
        }
    }

    // 查询学生
    private void searchStudent() {
        System.out.println("\n--- 查询学生 ---");
        System.out.println("1. 按学号查询");
        System.out.println("2. 按姓名查询");
        System.out.print("请选择查询方式: ");

        int choice = InputUtil.getIntInput("请选择查询方式", 1, 2);

        if (choice == 1) {
            String id = InputUtil.getStringInput("学号");
            Student student = studentDAO.getStudentById(id);
            if (student != null) {
                System.out.println("\n" + student);
            } else {
                System.out.println("学生不存在");
            }
        } else {
            String name = InputUtil.getStringInput("姓名");
            List<Student> students = studentDAO.getStudentsByName(name);
            if (!students.isEmpty()) {
                System.out.println("\n学号\t姓名\t年龄\t性别\t班级\t成绩");
                System.out.println("----------------------------------------");
                for (Student student : students) {
                    System.out.printf("%s\t%s\t%d\t%s\t%s\t%.1f%n",
                                student.getId(), student.getName(), student.getAge(),
                                student.getGender(), student.getClassName(), student.getScore());
                }
            } else {
                System.out.println("未找到匹配的学生");
            }
        }
    }

    // 更新学生
    private void updateStudent() {
        System.out.println("\n--- 更新学生 ---");
        String id = InputUtil.getStringInput("学号");
        Student student = studentDAO.getStudentById(id);

        if (student == null) {
            System.out.println("学生不存在");
            return;
        }

        System.out.println("当前信息: " + student);

        String name = InputUtil.getStringInput("新姓名(直接回车保持原值)", student.getName());

        int age = InputUtil.getIntInput("新年龄(直接回车保持原值)", student.getAge(), 1, 120);
        String gender = InputUtil.getStringInput("新性别(直接回车保持原值)", student.getGender());

        String className = InputUtil.getStringInput("新班级(直接回车保持原值)", student.getClassName());

        double score = InputUtil.getDoubleInput("新成绩(直接回车保持原值)", student.getScore(), 0, 100);

        Student updatedStudent = new Student(id, name, age, gender, className, score);
        studentDAO.updateStudent(updatedStudent);
    }

    // 删除学生
    private void deleteStudent() {
        System.out.println("\n--- 删除学生 ---");
        String id = InputUtil.getStringInput("学号");
        studentDAO.deleteStudent(id);
    }

    // 显示统计信息
    private void showStatistics() {
        System.out.println("\n--- 统计信息 ---");
        System.out.println("学生总数: " + studentDAO.getStudentCount());
        System.out.printf("平均成绩: %.2f%n", studentDAO.getAverageScore());
    }

    // 保存数据
    private void saveData() {
        System.out.println("\n--- 保存数据 ---");
        String filename = InputUtil.getStringInput("文件名");
        FileUtil.saveStudents(studentDAO.getAllStudents(), filename);
    }

    // 加载数据
    private void loadData() {
        System.out.println("\n--- 加载数据 ---");
        String filename = InputUtil.getStringInput("文件名");
        List<Student> students = FileUtil.loadStudents(filename);

        if (students != null) {
            studentDAO = new StudentDAO();
            for (Student student : students) {
                studentDAO.addStudent(student);
            }
            System.out.println("数据加载成功,共" + students.size() + "条记录");
        }
    }
}

4. 工具类

Java
// InputUtil.java — 输入工具类
// 封装控制台输入的读取和验证逻辑,支持类型转换、范围校验和默认值
package main.util;

import java.util.Scanner;

public class InputUtil {
    // 使用 static Scanner 实现全局共享(单例模式简化版)
    private static Scanner scanner = new Scanner(System.in);

    // 读取字符串输入(必填)
    public static String getStringInput(String prompt) {
        System.out.print(prompt + ": ");
        return scanner.nextLine().trim(); // trim() 去除首尾空白字符
    }

    // 读取字符串输入(可选,支持默认值)—— 方法重载
    // 用户直接回车时返回 defaultValue
    public static String getStringInput(String prompt, String defaultValue) {
        System.out.print(prompt + " [" + defaultValue + "]: ");
        String input = scanner.nextLine().trim();
        return input.isEmpty() ? defaultValue : input; // 三元运算符判断是否使用默认值
    }

    // 读取整数输入 —— while(true) + try-catch 实现输入验证循环
    // 不断提示直到用户输入有效的整数值
    public static int getIntInput(String prompt, int min, int max) {
        while (true) {
            try {
                System.out.print(prompt + ": ");
                int value = Integer.parseInt(scanner.nextLine().trim());
                if (value >= min && value <= max) {
                    return value;
                }
                System.out.println("输入必须在" + min + "到" + max + "之间");
            } catch (NumberFormatException e) {
                System.out.println("请输入有效的整数");
            }
        }
    }

    // 读取整数输入(可选,支持默认值)—— 重载方法
    // 输入为空时返回默认值;输入无效或越界时也回退到默认值(容错设计)
    public static int getIntInput(String prompt, int defaultValue, int min, int max) {
        System.out.print(prompt + " [" + defaultValue + "]: ");
        String input = scanner.nextLine().trim();
        if (input.isEmpty()) {
            return defaultValue;
        }
        try {
            int value = Integer.parseInt(input);
            if (value >= min && value <= max) {
                return value;
            }
            System.out.println("输入必须在" + min + "到" + max + "之间,使用默认值");
        } catch (NumberFormatException e) {
            System.out.println("输入无效,使用默认值");
        }
        return defaultValue;
    }

    // 读取浮点数输入 —— 与 getIntInput 类似,使用 Double.parseDouble 解析
    public static double getDoubleInput(String prompt, double min, double max) {
        while (true) {
            try {
                System.out.print(prompt + ": ");
                double value = Double.parseDouble(scanner.nextLine().trim());
                if (value >= min && value <= max) {
                    return value;
                }
                System.out.println("输入必须在" + min + "到" + max + "之间");
            } catch (NumberFormatException e) {
                System.out.println("请输入有效的数字");
            }
        }
    }

    public static double getDoubleInput(String prompt, double defaultValue, double min, double max) {
        System.out.print(prompt + " [" + defaultValue + "]: ");
        String input = scanner.nextLine().trim();
        if (input.isEmpty()) {
            return defaultValue;
        }
        try {
            double value = Double.parseDouble(input);
            if (value >= min && value <= max) {
                return value;
            }
            System.out.println("输入必须在" + min + "到" + max + "之间,使用默认值");
        } catch (NumberFormatException e) {
            System.out.println("输入无效,使用默认值");
        }
        return defaultValue;
    }
}
Java
// FileUtil.java — 文件操作工具类
// 使用 Java 序列化机制(ObjectOutputStream/ObjectInputStream)实现数据持久化
package main.util;

import main.model.Student;
import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class FileUtil {
    // 将学生列表序列化保存到文件
    // try-with-resources 语法自动关闭流,避免资源泄漏
    public static void saveStudents(List<Student> students, String filename) {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename))) {
            // writeObject 将整个 List 对象序列化为字节流写入文件
            // 要求 Student 类必须实现 Serializable 接口
            oos.writeObject(students);
            System.out.println("数据保存成功: " + filename);
        } catch (IOException e) {
            System.out.println("保存失败: " + e.getMessage());
        }
    }

    // 从文件反序列化加载学生列表
    // 多重 catch 分别处理不同异常类型,提供精确的错误提示
    public static List<Student> loadStudents(String filename) {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename))) {
            // 泛型类型在运行时会被擦除,强制转换产生未检查警告,用注解抑制
            @SuppressWarnings("unchecked")
            List<Student> students = (List<Student>) ois.readObject();
            System.out.println("数据加载成功: " + filename);
            return students;
        } catch (FileNotFoundException e) {
            System.out.println("文件不存在: " + filename);  // 首次运行时文件尚未创建
            return null;
        } catch (IOException e) {
            System.out.println("加载失败: " + e.getMessage()); // 文件损坏或IO异常
            return null;
        } catch (ClassNotFoundException e) {
            System.out.println("类未找到: " + e.getMessage()); // Student 类结构变更时可能出现
            return null;
        }
    }
}

5. 主程序

Java
// Main.java — 程序入口类
// 遵循 MVC 分层:Main(入口) → Service(业务) → DAO(数据) → Model(模型)
package main;

import main.service.StudentService;

public class Main {
    // main 方法是 Java 程序的唯一入口点
    public static void main(String[] args) {
        // 打印欢迎界面
        System.out.println("=========================================");
        System.out.println("       学生管理系统");
        System.out.println("=========================================");

        // 创建服务层实例并启动主菜单循环
        StudentService service = new StudentService();
        service.showMainMenu();
    }
}

📝 项目总结

完成的功能

  1. ✅ 学生信息管理(增删改查)
  2. ✅ 数据持久化(文件存储)
  3. ✅ 数据统计功能
  4. ✅ 用户友好的界面
  5. ✅ 输入验证
  6. ✅ 异常处理

学到的知识

通过这个项目,你实践了: - 面向对象编程的实际应用 - 集合框架的使用 - IO流的文件操作 - 异常处理机制 - 模块化程序设计

扩展建议

你可以继续扩展这个学生管理系统: - 添加课程管理功能 - 实现成绩分析功能 - 添加用户登录系统 - 实现数据备份和恢复 - 添加图形用户界面(GUI) - 实现数据库存储 - 添加报表生成功能

📚 推荐资源

🎉 恭喜完成!

你已经成功构建了一个完整的Java学生管理系统!这个项目展示了Java编程的核心概念和最佳实践。继续学习和实践,你将能够构建更复杂、更强大的Java应用。