实战项目 - 学生管理系统¶
📖 项目简介¶
本章将通过构建一个完整的学生管理系统,综合运用前面学到的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();
}
}
📝 项目总结¶
完成的功能¶
- ✅ 学生信息管理(增删改查)
- ✅ 数据持久化(文件存储)
- ✅ 数据统计功能
- ✅ 用户友好的界面
- ✅ 输入验证
- ✅ 异常处理
学到的知识¶
通过这个项目,你实践了: - 面向对象编程的实际应用 - 集合框架的使用 - IO流的文件操作 - 异常处理机制 - 模块化程序设计
扩展建议¶
你可以继续扩展这个学生管理系统: - 添加课程管理功能 - 实现成绩分析功能 - 添加用户登录系统 - 实现数据备份和恢复 - 添加图形用户界面(GUI) - 实现数据库存储 - 添加报表生成功能
📚 推荐资源¶
🎉 恭喜完成!¶
你已经成功构建了一个完整的Java学生管理系统!这个项目展示了Java编程的核心概念和最佳实践。继续学习和实践,你将能够构建更复杂、更强大的Java应用。