一、回顾与本篇目标
从第一篇到现在,你从零开始学了 Java 的完整基础知识:
- 基础语法:变量、数据类型、运算符、条件判断、循环
- 数据结构:数组——固定大小的数据集合
- 面向对象:类与对象、封装、继承、多态
- 工具类:String——文本处理、ArrayList——动态数组
- 文件操作:读写文件、异常处理
这些知识每一个单独拿出来都能用,但它们在你的脑子里可能是分散的。这一篇,我们要把它们全部串在一起,从零搭建一个完整的学生管理系统。
这个系统虽然规模不大,但它具备了真实项目的所有要素:用类来组织代码、用 ArrayList 管理数据、用文件读写实现持久化、用异常处理保证程序健壮。学完这一篇,你就能自信地说“我能用 Java 独立开发项目了”。
本篇的目标:
- 掌握从需求出发设计类和方法
- 综合运用封装、ArrayList、文件读写、异常处理
- 理解一个完整项目的代码结构
- 亲手写一个能增删改查、数据持久化的管理程序
二、需求分析——先想清楚再动手
在写代码之前,先把功能列清楚。学生管理系统需要以下功能:
- 添加学生:输入学号、姓名、年龄、成绩
- 查看所有学生:以列表形式展示
- 查找学生:按学号查找并显示详细信息
- 删除学生:按学号删除
- 数据持久化:程序启动时自动加载文件中的数据,程序退出前自动保存
技术选型:
- 用
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 零基础入门,全系列完。












暂无评论内容