28. 【Java】阶段实战:学生管理系统(OOP 版)

摘要: 本文通过一个完整的Java学生管理系统实战项目,系统性地展示了面向对象编程三大特性(封装、继承、多态)的实际应用。从需求分析、系统设计到逐步实现,详细讲解了如何使用抽象类、枚举、自定义异常等核心概念构建健壮的应用程序。项目包含人员管理、课程管理、选课与成绩三大模块,提供了完整的命令行交互界面和异常处理机制,帮助读者将理论知识转化为实践能力。

关键词: Java面向对象编程, 学生管理系统, 封装继承多态, 自定义异常, 枚举类


在过去的十一篇文章里,我们深入学习了面向对象编程的三大特性:封装、继承、多态。我们还认识了抽象类、接口、内部类、枚举、Record,以及异常处理。如果把写程序比作盖房子,那你现在手里已经有一套相当完整的“工具箱”了——有设计图纸(类)、有施工规范(封装)、有预制件(继承)、有各种专用工具(接口、枚举、异常)。

知识点学了不少,但如果它们只是散落在各自的位置上,就像一套崭新的工具各自躺在工具箱里,你还没亲手用它们搭出过一栋完整的建筑。

今天,我们要做一件事:把第三阶段学到的所有知识,放到一个完整的项目中融会贯通

我们要写一个学生管理系统——它不是什么大型企业应用,但足以让你感受到面向对象编程是如何把现实世界中的“学生”、“老师”、“课程”、“成绩”这些概念,优雅地映射到代码中的。


1. 我们要做什么?—— 需求分析

这是一个命令行版的学生管理系统,它要能管理学校里的基本数据。我们把它设计成三个核心模块:

模块一:人员管理

  • 学校里有两种人:学生老师
  • 学生有:姓名、年龄、学号、年级(大一/大二/大三/大四)。
  • 老师有:姓名、年龄、工号、职称(助教/讲师/副教授/教授)。
  • 系统能添加学生/老师,能列出所有学生/老师。

模块二:课程管理

  • 课程有:课程编号、课程名称、学分、授课老师。
  • 系统能添加课程,能列出所有课程。

模块三:选课与成绩

  • 学生可以选修课程(一个学生可以选多门课)。
  • 老师可以为选修了自己课程的学生录入成绩(0~100 分)。
  • 系统能查询某个学生的所有课程和成绩,能计算平均分。

此外,我们还要考虑:

  • 枚举来表示年级、职称、性别。
  • 自定义异常来处理业务错误(比如学生不存在、选课重复、成绩越界)。
  • List 来存储数据(虽然集合框架是下一阶段的内容,但我们今天先简单地用 ArrayList,它很好上手,和数组类似但更灵活)。

这个项目的复杂度恰到好处——它足够让你体会到面向对象设计的精妙,又不会因为功能太多而迷失在细节里。


2. 系统设计 —— 先画出“骨架”

在写代码之前,我们先设计一下类之间的关系。面向对象编程的核心就是先设计结构,再填充实现

类图(文字版)
┌──────────────────┐
│   Person         │  ← 抽象类
│  - name: String  │
│  - age: int      │
│  - gender: Gender│
│  + introduce()   │
└────────┬─────────┘
         │
    ┌────┴────┐
    │         │
┌────▼─────┐ ┌──▼──────────┐
│Student   │ │ Teacher     │
│-studentId│ │-employeeId  │
│-grade    │ │-title       │
│-courses  │ │+ teach()    │
│+ enroll()│ │             │
└──────────┘ └─────────────┘

┌─────────────────┐       ┌─────────────────┐
│    Course       │       │   GradeRecord   │
│  - courseId     │◄──────│  - student      │
│  - name         │       │  - course       │
│  - credit       │       │  - score        │
│  - teacher      │       └─────────────────┘
│  + addStudent() │
│  + getStudents()│
└─────────────────┘
核心设计思路
  1. Person 抽象类:作为学生和老师的共同父类,封装了姓名、年龄、性别,定义了抽象方法 introduce(),让每个子类以自己的方式介绍自己。
  2. StudentTeacher 子类:继承 Person,添加各自特有的属性和方法。
  3. Course:表示一门课程,包含课程信息和授课老师,维护一个选了该课的学生列表。
  4. GradeRecord:表示一条成绩记录,关联一个学生和一门课程,记录分数。
  5. GenderGradeLevelTitle 枚举:用枚举来约束取值范围,保证类型安全。
  6. 自定义异常StudentNotFoundExceptionDuplicateEnrollmentExceptionInvalidScoreException
  7. SchoolManager 主控类:包含 main 方法,提供命令行交互菜单。

3. 逐步实现

我们从最底层开始,一层一层往上搭建。

第一步:枚举类

先用枚举来定义那些“固定选项”。

// Gender.java
package com.example.school;

public enum Gender {
    MALE("男"),
    FEMALE("女");

    private final String chineseName;

    Gender(String chineseName) {
        this.chineseName = chineseName;
    }

    public String getChineseName() {
        return chineseName;
    }
}
// GradeLevel.java
package com.example.school;

public enum GradeLevel {
    FRESHMAN("大一"),
    SOPHOMORE("大二"),
    JUNIOR("大三"),
    SENIOR("大四");

    private final String chineseName;

    GradeLevel(String chineseName) {
        this.chineseName = chineseName;
    }

    public String getChineseName() {
        return chineseName;
    }
}
// Title.java
package com.example.school;

public enum Title {
    ASSISTANT("助教"),
    LECTURER("讲师"),
    ASSOCIATE_PROFESSOR("副教授"),
    PROFESSOR("教授");

    private final String chineseName;

    Title(String chineseName) {
        this.chineseName = chineseName;
    }

    public String getChineseName() {
        return chineseName;
    }
}

这些枚举都带了一个 chineseName 字段,方便在输出时显示中文,而不是英文常量名。

第二步:抽象类 Person
// Person.java
package com.example.school;

public abstract class Person {
    protected String name;
    protected int age;
    protected Gender gender;

    public Person(String name, int age, Gender gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public String getName() { return name; }
    public int getAge() { return age; }
    public Gender getGender() { return gender; }

    // 抽象方法:让子类各自实现介绍方式
    public abstract String introduce();

    @Override
    public String toString() {
        return String.format("%s (%s, %d岁)", name, gender.getChineseName(), age);
    }
}

这里用 protected 修饰字段,让子类可以直接访问(但更好的做法是用 getter,我们这里为了代码简洁用了 protected)。

第三步:自定义异常

我们先定义几个业务异常,让程序能精准地表达错误。

// StudentNotFoundException.java
package com.example.school;

public class StudentNotFoundException extends Exception {
    public StudentNotFoundException(String studentId) {
        super("学生不存在:" + studentId);
    }
}
// DuplicateEnrollmentException.java
package com.example.school;

public class DuplicateEnrollmentException extends Exception {
    public DuplicateEnrollmentException(String studentId, String courseId) {
        super("学生 " + studentId + " 已选修课程 " + courseId);
    }
}
// InvalidScoreException.java
package com.example.school;

public class InvalidScoreException extends Exception {
    public InvalidScoreException(double score) {
        super("成绩必须在 0~100 之间:" + score);
    }
}

这些异常都继承自 Exception(受检异常),强制调用方处理。

第四步:Student 类
// Student.java
package com.example.school;

import java.util.ArrayList;
import java.util.List;

public class Student extends Person {
    private String studentId;
    private GradeLevel grade;
    // 该学生选修的课程列表
    private List<Course> enrolledCourses;

    public Student(String name, int age, Gender gender, String studentId, GradeLevel grade) {
        super(name, age, gender);
        this.studentId = studentId;
        this.grade = grade;
        this.enrolledCourses = new ArrayList<>();
    }

    public String getStudentId() { return studentId; }
    public GradeLevel getGrade() { return grade; }
    public void setGrade(GradeLevel grade) { this.grade = grade; }

    public List<Course> getEnrolledCourses() {
        return new ArrayList<>(enrolledCourses);  // 返回副本,保护内部数据
    }

    // 选课
    public void enroll(Course course) throws DuplicateEnrollmentException {
        if (enrolledCourses.contains(course)) {
            throw new DuplicateEnrollmentException(studentId, course.getCourseId());
        }
        enrolledCourses.add(course);
        course.addStudent(this);  // 双向关联:课程也记录这个学生
    }

    // 获取某门课的成绩
    public Double getScoreForCourse(Course course) {
        for (GradeRecord record : course.getGradeRecords()) {
            if (record.getStudent().equals(this)) {
                return record.getScore();
            }
        }
        return null;  // 还没有成绩
    }

    // 计算平均分(只计算有成绩的课程)
    public double calculateAverage() {
        List<GradeRecord> allRecords = new ArrayList<>();
        for (Course course : enrolledCourses) {
            allRecords.addAll(course.getGradeRecords());
        }
        // 筛选出属于该学生的成绩记录
        double sum = 0;
        int count = 0;
        for (GradeRecord record : allRecords) {
            if (record.getStudent().equals(this)) {
                sum += record.getScore();
                count++;
            }
        }
        return count == 0 ? 0 : sum / count;
    }

    @Override
    public String introduce() {
        return String.format("我是学生 %s,学号 %s,%s %s",
                name, studentId, grade.getChineseName(), gender.getChineseName());
    }

    @Override
    public String toString() {
        return String.format("%s [%s, %s]", super.toString(), studentId, grade.getChineseName());
    }
}

注意这里用了双向关联Student 持有 enrolledCoursesCourse 也持有选修了它的学生列表。这种设计让查询变得灵活:既可以从学生查到课程,也可以从课程查学生。

第五步:Teacher 类
// Teacher.java
package com.example.school;

public class Teacher extends Person {
    private String employeeId;
    private Title title;

    public Teacher(String name, int age, Gender gender, String employeeId, Title title) {
        super(name, age, gender);
        this.employeeId = employeeId;
        this.title = title;
    }

    public String getEmployeeId() { return employeeId; }
    public Title getTitle() { return title; }
    public void setTitle(Title title) { this.title = title; }

    @Override
    public String introduce() {
        return String.format("我是老师 %s,工号 %s,%s",
                name, employeeId, title.getChineseName());
    }

    @Override
    public String toString() {
        return String.format("%s [%s, %s]", super.toString(), employeeId, title.getChineseName());
    }
}
第六步:Course 类
// Course.java
package com.example.school;

import java.util.ArrayList;
import java.util.List;

public class Course {
    private String courseId;
    private String name;
    private double credit;
    private Teacher teacher;
    // 选修该课程的学生列表
    private List<Student> students;
    // 成绩记录列表
    private List<GradeRecord> gradeRecords;

    public Course(String courseId, String name, double credit, Teacher teacher) {
        this.courseId = courseId;
        this.name = name;
        this.credit = credit;
        this.teacher = teacher;
        this.students = new ArrayList<>();
        this.gradeRecords = new ArrayList<>();
    }

    public String getCourseId() { return courseId; }
    public String getName() { return name; }
    public double getCredit() { return credit; }
    public Teacher getTeacher() { return teacher; }

    public List<Student> getStudents() {
        return new ArrayList<>(students);
    }

    public List<GradeRecord> getGradeRecords() {
        return new ArrayList<>(gradeRecords);
    }

    // 添加学生(由 Student.enroll() 调用)
    public void addStudent(Student student) {
        if (!students.contains(student)) {
            students.add(student);
        }
    }

    // 录入成绩
    public void assignGrade(Student student, double score) throws InvalidScoreException {
        if (score < 0 || score > 100) {
            throw new InvalidScoreException(score);
        }
        if (!students.contains(student)) {
            throw new IllegalArgumentException("该学生没有选修这门课程:" + student.getStudentId());
        }

        // 如果已有成绩记录,更新;否则新增
        for (GradeRecord record : gradeRecords) {
            if (record.getStudent().equals(student)) {
                record.setScore(score);
                return;
            }
        }
        gradeRecords.add(new GradeRecord(student, this, score));
    }

    // 获取某个学生的成绩
    public Double getStudentScore(Student student) {
        for (GradeRecord record : gradeRecords) {
            if (record.getStudent().equals(student)) {
                return record.getScore();
            }
        }
        return null;
    }

    @Override
    public String toString() {
        return String.format("%s [%s] %s学分, 授课教师: %s",
                courseId, name, credit, teacher.getName());
    }
}
第七步:GradeRecord 类(关联类)
// GradeRecord.java
package com.example.school;

public class GradeRecord {
    private Student student;
    private Course course;
    private double score;

    public GradeRecord(Student student, Course course, double score) {
        this.student = student;
        this.course = course;
        this.score = score;
    }

    public Student getStudent() { return student; }
    public Course getCourse() { return course; }
    public double getScore() { return score; }
    public void setScore(double score) { this.score = score; }

    @Override
    public String toString() {
        return String.format("%s - %s: %.1f分", student.getName(), course.getName(), score);
    }
}

GradeRecord 是一个关联类,它把学生和课程关联起来,同时携带了“成绩”这个额外的属性。这种设计比在 StudentCourse 中单独维护成绩更清晰。

第八步:SchoolManager 主控类(菜单 + 交互)
// SchoolManager.java
package com.example.school;

import java.util.*;

public class SchoolManager {
    private List<Student> students = new ArrayList<>();
    private List<Teacher> teachers = new ArrayList<>();
    private List<Course> courses = new ArrayList<>();
    private Scanner scanner = new Scanner(System.in);

    public static void main(String[] args) {
        SchoolManager manager = new SchoolManager();
        manager.run();
    }

    public void run() {
        // 预先添加一些示例数据
        initSampleData();

        while (true) {
            showMenu();
            String choice = scanner.nextLine().trim();

            switch (choice) {
                case "1" -> addStudent();
                case "2" -> addTeacher();
                case "3" -> addCourse();
                case "4" -> listStudents();
                case "5" -> listTeachers();
                case "6" -> listCourses();
                case "7" -> enrollStudent();
                case "8" -> assignGrade();
                case "9" -> showStudentReport();
                case "0" -> {
                    System.out.println("感谢使用学生管理系统,再见!");
                    return;
                }
                default -> System.out.println("无效选项,请重新输入。");
            }
        }
    }

    private void showMenu() {
        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("9. 查看学生成绩报告");
        System.out.println("0. 退出");
        System.out.print("请选择操作:");
    }

    // ---------- 添加数据 ----------
    private void addStudent() {
        System.out.print("姓名:");
        String name = scanner.nextLine();
        System.out.print("年龄:");
        int age = Integer.parseInt(scanner.nextLine());
        System.out.print("性别 (MALE/FEMALE):");
        Gender gender = Gender.valueOf(scanner.nextLine().toUpperCase());
        System.out.print("学号:");
        String id = scanner.nextLine();
        System.out.print("年级 (FRESHMAN/SOPHOMORE/JUNIOR/SENIOR):");
        GradeLevel grade = GradeLevel.valueOf(scanner.nextLine().toUpperCase());

        Student s = new Student(name, age, gender, id, grade);
        students.add(s);
        System.out.println("✅ 学生添加成功:" + s.introduce());
    }

    private void addTeacher() {
        System.out.print("姓名:");
        String name = scanner.nextLine();
        System.out.print("年龄:");
        int age = Integer.parseInt(scanner.nextLine());
        System.out.print("性别 (MALE/FEMALE):");
        Gender gender = Gender.valueOf(scanner.nextLine().toUpperCase());
        System.out.print("工号:");
        String id = scanner.nextLine();
        System.out.print("职称 (ASSISTANT/LECTURER/ASSOCIATE_PROFESSOR/PROFESSOR):");
        Title title = Title.valueOf(scanner.nextLine().toUpperCase());

        Teacher t = new Teacher(name, age, gender, id, title);
        teachers.add(t);
        System.out.println("✅ 老师添加成功:" + t.introduce());
    }

    private void addCourse() {
        System.out.print("课程编号:");
        String id = scanner.nextLine();
        System.out.print("课程名称:");
        String name = scanner.nextLine();
        System.out.print("学分:");
        double credit = Double.parseDouble(scanner.nextLine());

        // 选择授课老师
        if (teachers.isEmpty()) {
            System.out.println("❌ 请先添加老师!");
            return;
        }
        System.out.println("可选老师:");
        for (int i = 0; i < teachers.size(); i++) {
            System.out.println((i + 1) + ". " + teachers.get(i));
        }
        System.out.print("选择授课老师编号:");
        int idx = Integer.parseInt(scanner.nextLine()) - 1;
        Teacher teacher = teachers.get(idx);

        Course c = new Course(id, name, credit, teacher);
        courses.add(c);
        System.out.println("✅ 课程添加成功:" + c);
    }

    // ---------- 查询 ----------
    private void listStudents() {
        System.out.println("\n--- 所有学生 ---");
        if (students.isEmpty()) {
            System.out.println("暂无学生数据。");
            return;
        }
        for (Student s : students) {
            System.out.println("  " + s + " | " + s.introduce());
        }
    }

    private void listTeachers() {
        System.out.println("\n--- 所有老师 ---");
        if (teachers.isEmpty()) {
            System.out.println("暂无老师数据。");
            return;
        }
        for (Teacher t : teachers) {
            System.out.println("  " + t + " | " + t.introduce());
        }
    }

    private void listCourses() {
        System.out.println("\n--- 所有课程 ---");
        if (courses.isEmpty()) {
            System.out.println("暂无课程数据。");
            return;
        }
        for (Course c : courses) {
            System.out.println("  " + c + " | 选课人数:" + c.getStudents().size());
        }
    }

    // ---------- 业务操作 ----------
    private Student findStudentById(String id) {
        for (Student s : students) {
            if (s.getStudentId().equals(id)) {
                return s;
            }
        }
        return null;
    }

    private Course findCourseById(String id) {
        for (Course c : courses) {
            if (c.getCourseId().equals(id)) {
                return c;
            }
        }
        return null;
    }

    private void enrollStudent() {
        try {
            System.out.print("请输入学生学号:");
            String studentId = scanner.nextLine();
            Student student = findStudentById(studentId);
            if (student == null) {
                throw new StudentNotFoundException(studentId);
            }

            System.out.print("请输入课程编号:");
            String courseId = scanner.nextLine();
            Course course = findCourseById(courseId);
            if (course == null) {
                System.out.println("❌ 课程不存在:" + courseId);
                return;
            }

            student.enroll(course);
            System.out.println("✅ " + student.getName() + " 成功选修 " + course.getName());

        } catch (DuplicateEnrollmentException e) {
            System.out.println("❌ " + e.getMessage());
        } catch (StudentNotFoundException e) {
            System.out.println("❌ " + e.getMessage());
        }
    }

    private void assignGrade() {
        try {
            System.out.print("请输入学生学号:");
            String studentId = scanner.nextLine();
            Student student = findStudentById(studentId);
            if (student == null) {
                throw new StudentNotFoundException(studentId);
            }

            System.out.print("请输入课程编号:");
            String courseId = scanner.nextLine();
            Course course = findCourseById(courseId);
            if (course == null) {
                System.out.println("❌ 课程不存在:" + courseId);
                return;
            }

            if (!student.getEnrolledCourses().contains(course)) {
                System.out.println("❌ 该学生未选修这门课程。");
                return;
            }

            System.out.print("请输入成绩 (0~100):");
            double score = Double.parseDouble(scanner.nextLine());

            course.assignGrade(student, score);
            System.out.println("✅ 成绩录入成功!");

        } catch (StudentNotFoundException e) {
            System.out.println("❌ " + e.getMessage());
        } catch (InvalidScoreException e) {
            System.out.println("❌ " + e.getMessage());
        } catch (IllegalArgumentException e) {
            System.out.println("❌ " + e.getMessage());
        }
    }

    private void showStudentReport() {
        System.out.print("请输入学生学号:");
        String studentId = scanner.nextLine();
        Student student = findStudentById(studentId);
        if (student == null) {
            System.out.println("❌ 学生不存在:" + studentId);
            return;
        }

        System.out.println("\n========== 成绩报告 ==========");
        System.out.println("学生:" + student.getName() + " (" + student.getStudentId() + ")");
        System.out.println("年级:" + student.getGrade().getChineseName());
        System.out.println("选修课程:");

        List<Course> enrolled = student.getEnrolledCourses();
        if (enrolled.isEmpty()) {
            System.out.println("  (尚未选修任何课程)");
            return;
        }

        for (Course course : enrolled) {
            Double score = student.getScoreForCourse(course);
            String scoreDisplay = (score == null) ? "未录入" : String.format("%.1f", score);
            System.out.printf("  %s [%s]:%s 学分,成绩:%s%n",
                    course.getCourseId(), course.getName(), course.getCredit(), scoreDisplay);
        }

        double avg = student.calculateAverage();
        System.out.printf("\n平均分:%.1f%n", avg);
        System.out.println("================================");
    }

    // ---------- 示例数据 ----------
    private void initSampleData() {
        // 添加老师
        Teacher t1 = new Teacher("张教授", 45, Gender.MALE, "T001", Title.PROFESSOR);
        Teacher t2 = new Teacher("李副教授", 38, Gender.FEMALE, "T002", Title.ASSOCIATE_PROFESSOR);
        teachers.add(t1);
        teachers.add(t2);

        // 添加课程
        Course c1 = new Course("C001", "Java程序设计", 4.0, t1);
        Course c2 = new Course("C002", "数据库原理", 3.0, t2);
        Course c3 = new Course("C003", "数据结构", 3.5, t1);
        courses.add(c1);
        courses.add(c2);
        courses.add(c3);

        // 添加学生
        Student s1 = new Student("王小明", 20, Gender.MALE, "S001", GradeLevel.SOPHOMORE);
        Student s2 = new Student("李小丽", 19, Gender.FEMALE, "S002", GradeLevel.FRESHMAN);
        Student s3 = new Student("赵大伟", 21, Gender.MALE, "S003", GradeLevel.JUNIOR);
        students.add(s1);
        students.add(s2);
        students.add(s3);

        // 给部分学生选课
        try {
            s1.enroll(c1);
            s1.enroll(c2);
            s2.enroll(c1);
            s3.enroll(c2);
            s3.enroll(c3);
        } catch (DuplicateEnrollmentException e) {
            // 示例数据不会重复,忽略
        }

        // 录入一些成绩
        try {
            c1.assignGrade(s1, 85.0);
            c2.assignGrade(s1, 90.0);
            c1.assignGrade(s2, 78.0);
            c2.assignGrade(s3, 92.0);
        } catch (InvalidScoreException e) {
            // 示例数据分数合法,忽略
        }

        System.out.println("✅ 示例数据加载完成。");
    }
}

4. 运行与测试

把以上所有类放到 com.example.school 包下,运行 SchoolManager.main()。你会看到菜单,可以自由操作:

  1. 添加学生/老师/课程:体验用枚举来输入选项,感受类型安全带来的好处。
  2. 学生选课:选课功能会检查是否重复选课,抛出自定义异常。
  3. 录入成绩:录入时会检查成绩范围(0~100),不合规会抛出 InvalidScoreException
  4. 查看成绩报告:展示某个学生所有课程的成绩和平均分。

你可以尝试一些“错误操作”,比如录入一个超过 100 分的成绩、让一个未选课的学生录入成绩、重复选课等——观察程序是否都能优雅地处理,而不是崩溃。


5. 知识点回顾:我们用了什么?

这个不到 500 行的项目,几乎涵盖了第三阶段的所有核心概念:

知识点体现在哪里
封装所有类的字段都是 private,通过 public 方法访问
继承StudentTeacher 继承 Person
多态Person 引用可以指向 StudentTeacherintroduce() 动态绑定
抽象类Person 是抽象类,定义了模板,不允许直接实例化
接口(本项目中未显式使用,但你可以自己扩展,比如定义 Gradable 接口)
枚举GenderGradeLevelTitle 让取值范围可控
自定义异常StudentNotFoundExceptionDuplicateEnrollmentExceptionInvalidScoreException
异常处理try-catch、受检异常的声明与处理
集合ArrayList 存储学生、老师、课程、成绩记录
内部类(你可以把 GradeRecord 设计成 Course 的内部类,目前作为独立类更清晰)
包与导入所有类都放在 com.example.school 包下,使用 import 导入标准库
Record(你可以把 GradeRecord 改写成 Record,作为一个练习)

6. 如何扩展?(留给你的挑战)

这个系统还远未完善,你可以继续扩展它:

  1. 删除功能:添加删除学生、删除课程的功能(需要考虑关联数据)。
  2. 数据持久化:把数据保存到文件,下次启动时加载(我们会 I/O 时再讲)。
  3. 查找功能:按姓名、学号、课程名进行搜索。
  4. 统计分析:计算每门课程的平均分、最高分、最低分,按成绩排序等。
  5. 使用 Record 重构:把 GradeRecord 改写成 Record,感受代码量的变化。
  6. 增加接口:定义 Gradable 接口,让 Student 实现它,统一处理“有成绩”的对象。
  7. 增加用户输入校验:目前输入性别时如果输错会直接抛异常,可以加一个更友好的循环询问。

7. 写在最后

恭喜你完成了第三阶段的实战项目!

从第一篇的 Hello, World! 走到这里,你已经掌握了 Java 的核心——面向对象编程。你现在能够:

  • 用类来建模现实世界的事物。
  • 用封装来保护数据、隔离变化。
  • 用继承来复用代码、建立层次关系。
  • 用多态来写出灵活、可扩展的程序。
  • 用抽象类和接口来定义契约和模板。
  • 用枚举来表示固定选项。
  • 用异常处理来让程序更健壮。

你写的代码,已经从一个“顺序执行的脚本”,变成了一个有结构、有层次、有逻辑、有防御的“小系统”。这是质的飞跃。

从下一篇文章开始,我们将进入第四阶段:进阶篇。我们会学习 Java 的标准类库中那些最常用的工具:集合框架、泛型、I/O、日期时间、Lambda 和 Stream……这些会让你的 Java 能力再上一个台阶。

休息一下,喝杯水,然后继续前进吧。


动手试试

  1. 把整个学生管理系统跑起来,创建几个学生、老师、课程,完成一次完整的“选课 → 录入成绩 → 查看报告”的流程。
  2. SchoolManager 中增加一个“按课程查看成绩排名”的功能:输入课程编号,列出所有选修该课程的学生及其成绩,按分数从高到低排序。
  3. GradeRecord 改写成 Record,调整 Course 类中相关代码,体会 Record 带来的简洁。
  4. 增加一个 Advisor 接口(导师),包含 giveAdvice(Student student) 方法。让 Teacher 实现这个接口,表示老师可以给学生提供建议。然后在主菜单中增加“老师给学生建议”的功能。
  5. (挑战)思考这个系统的设计还有哪些可以改进的地方?比如 Student 中的 calculateAverage() 遍历了所有课程的成绩记录,效率是不是可以优化?CourseStudent 之间的双向关联,有没有更好的设计方式?

我们下个阶段见。😃

📌 获取本系列示例代码请访问 GitCode

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值