十一:综合实战——学生管理系统

一、回顾与本篇目标

从第一篇到现在,你从零开始学了 Java 的完整基础知识:

  • 基础语法:变量、数据类型、运算符、条件判断、循环
  • 数据结构:数组——固定大小的数据集合
  • 面向对象:类与对象、封装、继承、多态
  • 工具类:String——文本处理、ArrayList——动态数组
  • 文件操作:读写文件、异常处理

这些知识每一个单独拿出来都能用,但它们在你的脑子里可能是分散的。这一篇,我们要把它们全部串在一起,从零搭建一个完整的学生管理系统

这个系统虽然规模不大,但它具备了真实项目的所有要素:用类来组织代码、用 ArrayList 管理数据、用文件读写实现持久化、用异常处理保证程序健壮。学完这一篇,你就能自信地说“我能用 Java 独立开发项目了”。

本篇的目标:

  1. 掌握从需求出发设计类和方法
  2. 综合运用封装、ArrayList、文件读写、异常处理
  3. 理解一个完整项目的代码结构
  4. 亲手写一个能增删改查、数据持久化的管理程序

二、需求分析——先想清楚再动手

在写代码之前,先把功能列清楚。学生管理系统需要以下功能:

  1. 添加学生:输入学号、姓名、年龄、成绩
  2. 查看所有学生:以列表形式展示
  3. 查找学生:按学号查找并显示详细信息
  4. 删除学生:按学号删除
  5. 数据持久化:程序启动时自动加载文件中的数据,程序退出前自动保存

技术选型

  • Student 类封装学生数据(学号、姓名、年龄、成绩)
  • ArrayList<Student> 管理学生列表
  • BufferedReader/BufferedWriter 读写文件
  • Scanner 接收键盘输入
  • try/catch 处理异常

三、Scanner——从键盘读取用户输入

在开始项目之前,先学一个之前没用过的工具——Scanner。它可以从键盘读取用户输入的内容。之前我们的程序数据都是写死在代码里的,现在可以让用户在运行时动态输入了。

import java.util.Scanner;  // 导入 Scanner

public class ScannerDemo {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);  // 创建 Scanner 对象

        System.out.print("请输入你的名字:");
        String name = scanner.nextLine();  // 读取一行字符串

        System.out.print("请输入你的年龄:");
        int age = scanner.nextInt();       // 读取一个整数

        System.out.println("你好," + name + "!你明年就 " + (age + 1) + " 岁了。");

        scanner.close();  // 使用完毕后关闭
    }
}

Scanner 常用方法

方法 作用 示例
nextLine() 读取一行字符串(可以包含空格) String s = scanner.nextLine();
nextInt() 读取一个整数 int n = scanner.nextInt();
nextDouble() 读取一个小数 double d = scanner.nextDouble();
next() 读取一个单词(遇到空格就停止) String w = scanner.next();

一个重要陷阱nextInt() 读取完数字后,输入缓冲区里会残留一个换行符。如果紧跟着调用 nextLine(),这个换行符会被当作空行读走,导致你预期的输入被跳过。解决方法:在 nextInt() 后面多写一行 scanner.nextLine(),把残留的换行符消耗掉。

System.out.print("请输入年龄:");
int age = scanner.nextInt();
scanner.nextLine();  // 消耗残留的换行符

System.out.print("请输入姓名:");
String name = scanner.nextLine();  // 现在可以正常读取了

四、项目结构

这个项目由两个 Java 文件组成:

StudentManager/
├── Student.java          ← 学生类(数据模型)
└── StudentManager.java   ← 主程序(业务逻辑 + 文件读写 + 用户交互)

五、Step 1:定义 Student 类

// Student.java —— 学生数据模型

public class Student {
    private String id;      // 学号
    private String name;    // 姓名
    private int age;        // 年龄
    private double score;   // 成绩

    // 构造方法
    public Student(String id, String name, int age, double score) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.score = score;
    }

    // Getter 方法
    public String getId() { return this.id; }
    public String getName() { return this.name; }
    public int getAge() { return this.age; }
    public double getScore() { return this.score; }

    // Setter 方法(如果需要修改学生信息)
    public void setName(String name) { this.name = name; }
    public void setAge(int age) { this.age = age; }
    public void setScore(double score) { this.score = score; }

    // 判断是否及格
    public boolean isPassed() {
        return this.score >= 60;
    }

    // 转成文件存储格式:学号,姓名,年龄,成绩
    public String toFileString() {
        return this.id + "," + this.name + "," + this.age + "," + this.score;
    }

    // 从文件格式创建 Student 对象
    public static Student fromFileString(String line) {
        String[] parts = line.split(",");  // 按逗号分割
        if (parts.length != 4) {
            return null;  // 格式不对,返回 null
        }
        String id = parts[0];
        String name = parts[1];
        int age = Integer.parseInt(parts[2]);        // 字符串转整数
        double score = Double.parseDouble(parts[3]); // 字符串转小数
        return new Student(id, name, age, score);
    }

    // 打印格式
    @Override
    public String toString() {
        String grade;
        if (score >= 90) grade = "优秀";
        else if (score >= 80) grade = "良好";
        else if (score >= 70) grade = "中等";
        else if (score >= 60) grade = "及格";
        else grade = "不及格";

        return String.format("%-8s %-8s %-6d %-8.1f %-6s",
                id, name, age, score, grade);
    }
}

代码解析

  • 封装:所有属性都是 private,通过 getter/setter 访问。
  • toFileString():把学生数据转成 "001,张三,20,85.5" 格式的字符串,方便写入文件。
  • fromFileString():从文件中读取的一行字符串解析出学生对象。split(",") 按逗号分割字符串。如果格式不对(比如分割后不是 4 部分),返回 null 并跳过这一行。
  • toString():重写这个方法,让 System.out.println(student) 能输出格式化的信息。String.format()printf 类似,%-8s 表示左对齐、占 8 个字符宽度。

六、Step 2:实现主程序

// StudentManager.java —— 主程序

import java.io.*;
import java.util.ArrayList;
import java.util.Scanner;

public class StudentManager {
    // 存储所有学生
    private static ArrayList students = new ArrayList<>();
    // 数据文件路径
    private static final String FILENAME = "students.txt";
    // 键盘输入
    private static Scanner scanner = new Scanner(System.in);

    public static void main(String[] args) {
        // 1. 启动时加载文件数据
        loadFromFile();

        // 2. 主菜单循环
        while (true) {
            printMenu();
            System.out.print("请选择操作(1-5):");
            String choice = scanner.nextLine();

            switch (choice) {
                case "1":
                    addStudent();
                    break;
                case "2":
                    listAllStudents();
                    break;
                case "3":
                    searchStudent();
                    break;
                case "4":
                    deleteStudent();
                    break;
                case "5":
                    saveToFile();
                    System.out.println("数据已保存,程序退出。再见!");
                    scanner.close();
                    return;  // 退出程序
                default:
                    System.out.println("无效选择,请输入 1-5 之间的数字。");
            }
        }
    }

    // 打印菜单
    private static void printMenu() {
        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("==================================");
        System.out.println("当前共有 " + students.size() + " 名学生");
    }

    // 1. 添加学生
    private static void addStudent() {
        System.out.println("\n===== 添加学生 =====");
        System.out.print("请输入学号:");
        String id = scanner.nextLine();

        // 检查学号是否已存在
        for (Student s : students) {
            if (s.getId().equals(id)) {
                System.out.println("错误:学号 " + id + " 已存在!");
                return;
            }
        }

        System.out.print("请输入姓名:");
        String name = scanner.nextLine();

        System.out.print("请输入年龄:");
        int age;
        try {
            age = Integer.parseInt(scanner.nextLine());
            if (age < 0 || age > 150) {
                System.out.println("错误:年龄必须在 0-150 之间!");
                return;
            }
        } catch (NumberFormatException e) {
            System.out.println("错误:请输入有效的整数年龄!");
            return;
        }

        System.out.print("请输入成绩:");
        double score;
        try {
            score = Double.parseDouble(scanner.nextLine());
            if (score < 0 || score > 100) {
                System.out.println("错误:成绩必须在 0-100 之间!");
                return;
            }
        } catch (NumberFormatException e) {
            System.out.println("错误:请输入有效的数字成绩!");
            return;
        }

        students.add(new Student(id, name, age, score));
        System.out.println("学生 " + name + " 添加成功!");
    }

    // 2. 查看所有学生
    private static void listAllStudents() {
        System.out.println("\n===== 学生列表 =====");
        if (students.isEmpty()) {
            System.out.println("暂无学生数据。");
            return;
        }

        // 打印表头
        System.out.printf("%-8s %-8s %-6s %-8s %-6s%n", "学号", "姓名", "年龄", "成绩", "等级");
        System.out.println("-----------------------------------------");
        // 打印每个学生
        for (Student s : students) {
            System.out.println(s);  // 自动调用 toString()
        }
        System.out.println("共 " + students.size() + " 名学生");
    }

    // 3. 查找学生
    private static void searchStudent() {
        System.out.println("\n===== 查找学生 =====");
        System.out.print("请输入要查找的学号:");
        String id = scanner.nextLine();

        for (Student s : students) {
            if (s.getId().equals(id)) {
                System.out.println("找到了:");
                System.out.println("  学号:" + s.getId());
                System.out.println("  姓名:" + s.getName());
                System.out.println("  年龄:" + s.getAge() + " 岁");
                System.out.println("  成绩:" + s.getScore() + " 分");
                System.out.println("  是否及格:" + (s.isPassed() ? "是" : "否"));
                return;
            }
        }
        System.out.println("未找到学号为 " + id + " 的学生。");
    }

    // 4. 删除学生
    private static void deleteStudent() {
        System.out.println("\n===== 删除学生 =====");
        System.out.print("请输入要删除的学号:");
        String id = scanner.nextLine();

        for (int i = 0; i < students.size(); i++) {
            if (students.get(i).getId().equals(id)) {
                String name = students.get(i).getName();
                students.remove(i);
                System.out.println("学生 " + name + "(学号:" + id + ")已删除。");
                return;
            }
        }
        System.out.println("未找到学号为 " + id + " 的学生。");
    }

    // 5. 从文件加载数据
    private static void loadFromFile() {
        File file = new File(FILENAME);
        if (!file.exists()) {
            System.out.println("数据文件不存在,将创建新文件。");
            return;
        }

        try (BufferedReader reader = new BufferedReader(new FileReader(FILENAME))) {
            String line;
            int loadedCount = 0;
            int errorCount = 0;

            while ((line = reader.readLine()) != null) {
                line = line.trim();  // 去除首尾空白
                if (line.isEmpty()) continue;  // 跳过空行

                Student s = Student.fromFileString(line);
                if (s != null) {
                    students.add(s);
                    loadedCount++;
                } else {
                    errorCount++;
                }
            }

            System.out.println("从文件加载了 " + loadedCount + " 条学生记录"
                    + (errorCount > 0 ? "(" + errorCount + " 行格式错误,已跳过)" : ""));
        } catch (FileNotFoundException e) {
            System.out.println("数据文件不存在,将创建新文件。");
        } catch (IOException e) {
            System.out.println("读取文件出错:" + e.getMessage());
        }
    }

    // 6. 保存数据到文件
    private static void saveToFile() {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(FILENAME))) {
            for (Student s : students) {
                writer.write(s.toFileString());
                writer.newLine();
            }
            System.out.println("数据已保存,共 " + students.size() + " 条记录。");
        } catch (IOException e) {
            System.out.println("保存文件失败:" + e.getMessage());
        }
    }
}

七、完整测试流程

编译并运行程序:

javac Student.java StudentManager.java
java StudentManager

第一次运行时,因为没有 students.txt,程序会提示“数据文件不存在”。然后你可以通过菜单操作:

1. 添加学生:选择 1,依次输入学号、姓名、年龄、成绩。程序会验证:学号不能重复、年龄在 0-150 之间、成绩在 0-100 之间、输入必须是数字。

请选择操作(1-5):1

===== 添加学生 =====
请输入学号:001
请输入姓名:张三
请输入年龄:20
请输入成绩:85.5
学生 张三 添加成功!

2. 查看所有学生:选择 2,以表格形式展示所有学生,自动计算等级。

请选择操作(1-5):2

===== 学生列表 =====
学号     姓名     年龄   成绩     等级
-----------------------------------------
001      张三     20     85.5     良好
002      李四     21     92.0     优秀
003      王五     19     78.0     中等
共 3 名学生

3. 查找学生:选择 3,输入学号,显示该学生的详细信息。

4. 删除学生:选择 4,输入学号,删除对应学生。

5. 退出系统:选择 5,程序自动把当前所有数据保存到 students.txt 文件中,然后退出。

再次启动程序:程序会自动从 students.txt 加载上次保存的数据——之前添加的学生还在。

八、代码设计思路回顾

这个项目的代码虽然有两百多行,但结构非常清晰。回顾一下整体的设计思路:

分层思想

  • 数据模型层(Student.java):只负责描述“学生是什么”——有哪些属性,怎么从字符串创建对象,怎么转回字符串。不涉及任何业务逻辑和用户交互。
  • 业务逻辑层(StudentManager.java):负责所有操作——增删改查、文件读写、用户交互。

关键设计决策

  • 数据验证:添加学生时做了多层验证——学号是否重复、年龄是否合法、成绩是否合法、输入是否是数字。这些验证让程序更健壮,不会因为错误输入而崩溃。
  • 文件格式:用 CSV 格式(逗号分隔)存储数据。这种格式简单、通用、人类可读,用 Excel 也能打开。
  • 错误处理loadFromFile() 中对格式错误的行做了容错处理——跳过而不是崩溃。这在处理真实数据时非常重要。

九、可以继续完善的方向

这个系统已经具备了增删改查和数据持久化的基本功能,但还有很多可以改进的地方:

  • 修改学生信息:增加一个菜单选项,允许修改指定学生的姓名、年龄或成绩。
  • 排序功能:按学号、成绩、年龄排序显示。
  • 统计功能:显示平均分、最高分、最低分、及格率。
  • 分页显示:当学生数量很多时,分页展示。
  • 异常处理细化:对文件操作中可能的各种异常做更细致的分类处理。

十、本篇动手练习

练习 1:添加修改功能

在菜单中增加第 6 个选项“修改学生信息”。输入学号找到学生后,让用户选择修改姓名、年龄还是成绩(或全部修改)。

练习 2:添加排序功能

在“查看所有学生”之前,让用户选择排序方式:按学号、按成绩从高到低、按年龄从小到大。提示:ArrayList 可以用 Collections.sort() 配合自定义比较器进行排序。

练习 3:添加统计功能

增加菜单选项“显示统计信息”,输出:总人数、平均分、最高分及对应学生、最低分及对应学生、及格率。

练习 4:重构为面向对象的分层结构

把 StudentManager 中的业务逻辑拆分到独立的 StudentService 类中(负责增删改查),把文件读写拆分到 FileService 类中。让 main 方法只负责显示菜单和处理用户输入。这是 MVC 模式的雏形。

十一、本篇小结

这一篇你完成了学习 Java 以来的第一个完整项目:

  • 综合运用:类与对象、封装、ArrayList、文件读写、异常处理、Scanner 输入、条件判断、循环——几乎涵盖了本系列所有知识点。
  • 设计思路:从需求分析出发 → 设计数据模型 → 实现功能模块 → 整合运行。
  • 数据持久化:程序退出后数据还在,下次启动自动恢复——这是“实用工具”和“玩具程序”的分界线。
  • 用户交互:菜单驱动的交互方式,输入验证,友好的错误提示。

这个学生管理系统虽然规模不大,但它是一个“麻雀虽小,五脏俱全”的完整项目。把它吃透、自己重写一遍、然后添加新功能——这就是从“学语法”到“写程序”的关键一步。

十二、Java 系列总结——你的学习路线图

《Java 零基础入门》到这里就全部结束了。十一篇文章,从一行 Java 代码都不会写,到能独立开发一个带数据持久化的完整管理系统。你学到了:

阶段 内容 核心能力
基础语法 变量、数据类型、运算符、条件判断、循环 能写有逻辑的小程序
数据结构 数组——固定大小的数据集合 能批量处理数据
面向对象(上) 类与对象、成员变量、成员方法、构造方法 能用面向对象方式组织代码
面向对象(下) 封装、继承、方法重写、多态 能写出结构清晰、可复用的代码
常用工具 String、StringBuilder、ArrayList 能高效处理文本和动态数据
文件与异常 try/catch、文件读写 程序更健壮,数据可持久化
综合实战 学生管理系统 能从零搭建完整项目

下一步的学习方向

学完本系列之后,你可以往以下几个方向继续深入:

  • Java 集合框架:HashMap、HashSet、LinkedList 等更丰富的数据结构。
  • Java 数据库编程:JDBC 连接 MySQL,用 SQL 操作真正的数据库,替代文件存储。
  • Java Web 开发:Servlet、JSP、Spring Boot——用 Java 写后端服务。
  • Java 多线程:并发编程,处理高并发场景。

最重要的是——做项目。把你自己的需求变成程序:写一个日记本、一个记账软件、一个图书管理器。在解决实际问题的过程中,你学到的东西比看任何教程都深刻。

编程的路很长,但你已经迈出了最坚实的第一步。祝你在接下来的学习中,找到属于自己的节奏和乐趣。

——Java 零基础入门,全系列完。

© 版权声明
THE END
喜欢就支持一下吧
点赞13 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容