2026年8月4日-作业.md 34 KB

2026年8月4日 课后作业 — JavaSE 期末综合大作业:学生信息管理系统

说明:这是 JavaSE 阶段的收尾综合大作业,要求把整个阶段学过的知识点串成一套完整的学生信息管理系统,覆盖:数据类型与运算符、流程控制(分支/循环)、类与对象(封装/构造方法)、继承与多态、抽象类与接口、自定义异常、集合框架(ArrayList/Map/Set/泛型/迭代器)、比较器排序(Comparable/Comparator) 等核心内容;Lambda 表达式 / 函数式编程 / Stream 流 / 方法引用 作为"进阶挑战(选做)"给出实现建议,鼓励但不强制。

题目按 模块 划分:模块一 ~ 模块二(⭐ 必做) → 模块三 ~ 模块四(⭐⭐ 必做) → 模块五(⭐⭐⭐⭐ 挑战·选做),最后附 简答题(必做)。请新建 Java 项目,按模块逐步编写并运行,将运行结果截图与代码一并提交。


📌 作业总体要求

一、命名要求

项目 要求
包名 按下列"推荐包结构"创建(entity / exception / manager / util / main)
类名 自由命名,但需见名知意(如 Student、ScoreManager)
测试入口 主程序统一命名为 StudentSystemApp
文件头注释 每个 Java 文件头部需包含 @author、@title、@description、@create 文档注释

二、提交目录结构示例

提交\
└─ 张三\
    ├─ entity\                      ← 模块一:实体类 + 接口
    │   ├─ Person.java              (抽象类)
    │   ├─ Student.java
    │   ├─ Teacher.java
    │   ├─ Course.java
    │   ├─ Score.java
    │   ├─ Studyable.java           (接口)
    │   └─ Teachable.java           (接口)
    ├─ exception\                   ← 模块二:自定义异常
    │   ├─ ScoreOutOfBoundsException.java
    │   ├─ StudentNotFoundException.java
    │   ├─ CourseNotFoundException.java
    │   └─ DuplicateStudentException.java
    ├─ manager\                     ← 模块三:核心功能(集合 + 流程控制)
    │   ├─ StudentManager.java
    │   ├─ CourseManager.java
    │   └─ ScoreManager.java
    ├─ util\                        ← 模块四:排序与排名
    │   └─ RankUtil.java
    ├─ main\                        ← 模块三:主菜单入口
    │   └─ StudentSystemApp.java
    └─ 简答题.txt                   ← 简答题答案

⚠ 注意:包结构可微调(如合并包),但逻辑分层必须清晰:实体类 / 异常类 / 管理类 / 主程序要分开。


🏫 系统整体需求

业务背景

某学校需要一个控制台版学生信息管理系统,用于管理学生、教师、课程、成绩四类核心信息。要求通过 Scanner 键盘输入 + 菜单 交互,实现学生增删改查、课程维护、成绩录入与校验、成绩排名等功能。

系统实体关系(一句话版)

Person(抽象类:name / age)
  ├── Student(学号 id / 班级 clazz / 成绩列表 List<Score>)—— 学 → Studyable
  └── Teacher(工号 id / 教授课程 Course)                  —— 教 → Teachable
Course(课程编号 / 课程名 / 学分)
Score (学号 / 课程编号 / 成绩 double,0~100)

功能模块总览

模块 名称 必做程度 核心知识点
模块一 实体类设计(Person/Student/Teacher/Course/Score + 接口) ⭐ 必做 数据类型、类与对象、封装、构造方法、继承、抽象类、接口、多态
模块二 自定义异常体系 ⭐ 必做 自定义异常、throw/throws、try-catch、程序鲁棒性
模块三 系统核心功能(学生/课程/成绩管理 + 主菜单) ⭐⭐ 必做 集合框架、泛型、迭代器、Scanner、流程控制(switch/while/if/for)
模块四 排序与排名(Comparable / Comparator / TreeSet) ⭐⭐ 必做 比较器排序、二次比较、Collections、compareTo 与 equals/hashCode 一致性
模块五 函数式编程与 Stream 流高级查询 ⭐⭐⭐⭐ 选做·挑战 Lambda、Stream、方法引用(给出建议,不强制)
简答题 综合理论问答 必做 抽象类 vs 接口、equals/hashCode、异常机制等

知识覆盖地图

知识点 落地位置 说明
数据类型与运算符 模块一/三 String(学号/姓名/课程名)、int(年龄/学分)、double(成绩/平均分/及格率)、算术/比较/逻辑运算符
流程控制 模块三 主菜单 switch、外层 while 循环、输入校验 if-else、遍历 for / foreach
类与对象、封装 模块一 private 成员 + getter/setter、全参构造方法
继承 模块一 Student extends Person、Teacher extends Person、super(name, age)
抽象类 模块一 Person 抽象类 + 抽象方法 getRole()
接口 模块一 Studyable / Teachable 接口,实现类重写抽象方法
多态 模块三 Person[] / ArrayList<Person> 装 Student+Teacher,instanceof 向下转型
自定义异常 模块二/三 继承 Exception、throw 抛出、throws 声明、多 catch + finally
集合框架 模块三 ArrayList 学生/课程/成绩、HashMap<String, List<Score>> 按学号分组
泛型 模块三 ArrayList<Student> 等泛型集合
迭代器 模块三/四 Iterator 遍历集合
比较器排序 模块四 Comparable 自然排序、Comparator 匿名内部类 / Lambda、Collections.sort
equals/hashCode 模块一 以"业务主键"(学号/工号/学号+课程)重写
String 类 模块一/三 equals、compareTo、format 等
Lambda / Stream / 方法引用 模块五(选做) filter / map / count / distinct / collect(toMap) / 方法引用

模块一:实体类设计(⭐ 必做)

知识点:数据类型、类与对象、封装、构造方法、继承、抽象类、接口、多态、equals/hashCode/toString

包名:entity

1.1 Person 抽象类

要求定义一个抽象类 Person,作为"人"的公共抽象:

// Person.java
public abstract class Person {
    private String name;   // 姓名
    private int age;       // 年龄

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

    // TODO: getter / setter

    // TODO: 抽象方法 —— 返回角色描述("学生" / "教师")
    public abstract String getRole();

    @Override
    public String toString() {
        return "姓名:" + name + ",年龄:" + age;
    }
}

要求:

  1. name / age 用 private 封装,提供 getter/setter(封装知识点)。
  2. 抽象方法 getRole() 由子类实现。
  3. 重写 toString()(可结合 getClass().getSimpleName() 输出类名)。

1.2 接口:Studyable / Teachable

要求定义两个接口,分别规范"学生能学习、教师能授课":

// Studyable.java —— 学习能力接口
public interface Studyable {
    void study();   // 学习
}

// Teachable.java —— 教学能力接口
public interface Teachable {
    void teach();   // 授课
}

💡 建议(不强制):可以给接口再补一个 JDK8 默认方法,例如 default void prepare() { System.out.println("准备中..."); },体会接口默认方法的使用。

1.3 Student 学生类

Student extends Person implements Comparable<Student>, Studyable,核心要求:

// Student.java
import java.util.ArrayList;
import java.util.List;

public class Student extends Person implements Comparable<Student>, Studyable {
    private String id;                // 学号,如 S001
    private String clazz;             // 班级,如 "Java2301"
    private List<Score> scores;       // 该学生的成绩列表

    public Student(String name, int age, String id, String clazz) {
        super(name, age);             // 调用父类构造方法(继承知识点)
        this.id = id;
        this.clazz = clazz;
        this.scores = new ArrayList<>();
    }

    // TODO: getter / setter

    // TODO: addScore(Score s) —— 向成绩列表添加一条成绩

    // TODO: getTotalScore() —— 遍历 scores 累加总分(注意:scores 为空返回 0)
    // TODO: getAvgScore()   —— 总分 / 成绩条数(保留两位小数;无成绩返回 0)
    // TODO: getScoreCount() —— 返回成绩条数

    // TODO: 实现抽象方法 getRole() —— 返回 "学生"

    // TODO: 实现接口方法 study() —— 输出 "xxx 同学正在努力学习..."

    // TODO: 重写 compareTo —— 按平均分【降序】,平均分相同再按学号
    //   提示:int result = Double.compare(o.getAvgScore(), this.getAvgScore());
    //         return result == 0 ? this.id.compareTo(o.getId()) : result;

    // TODO: 重写 equals / hashCode —— 以【学号】判定同一人
    //   提示:equals 用 Objects.equals(id, student.id);hashCode 用 Objects.hash(id)

    // TODO: 重写 toString —— 输出 学号/姓名/年龄/班级/平均分
}

要求:

  1. 必须调用 super(name, age)(继承中构造方法的调用)。
  2. compareTo 用 Double.compare 实现降序 + 二次比较(学号)。
  3. equals/hashCode 以学号为判定依据,与 compareTo 判定口径一致。

1.4 Teacher 教师类

Teacher extends Person implements Teachable:

// Teacher.java
public class Teacher extends Person implements Teachable {
    private String id;        // 工号,如 T001
    private Course course;    // 教授的课程(对象关联)

    public Teacher(String name, int age, String id, Course course) {
        super(name, age);
        this.id = id;
        this.course = course;
    }

    // TODO: getter / setter

    // TODO: 实现 getRole() —— 返回 "教师"

    // TODO: 实现 teach() —— 输出 "xxx 老师正在讲授《课程名》..."

    // TODO: 重写 equals / hashCode —— 以【工号】判定

    // TODO: 重写 toString
}

1.5 Course 课程类

// Course.java
public class Course {
    private String courseId;    // 课程编号,如 C001
    private String courseName;  // 课程名称,如 "JavaSE 基础"
    private int credit;         // 学分

    public Course(String courseId, String courseName, int credit) {
        this.courseId = courseId;
        this.courseName = courseName;
        this.credit = credit;
    }

    // TODO: getter / setter
    // TODO: toString —— 输出 "C001-JavaSE 基础-3学分"
}

1.6 Score 成绩类

// Score.java
public class Score {
    private String studentId;   // 学号
    private String courseId;    // 课程编号
    private double score;       // 成绩(0~100)

    public Score(String studentId, String courseId, double score) throws ScoreOutOfBoundsException {
        this.studentId = studentId;
        this.courseId = courseId;
        setScore(score);        // 在 setter 中校验并抛出异常
    }

    // TODO: getter / setter —— setScore 中校验:score < 0 || score > 100 时
    //    throw new ScoreOutOfBoundsException("成绩必须在 0~100 之间,当前值:" + score);

    // TODO: equals / hashCode —— 以 studentId + courseId 判定(同一学生同一课程视为一条成绩)

    // TODO: toString
}

📌 注意:Score 的构造方法签名带 throws ScoreOutOfBoundsException——这里引出了模块二的异常类,请先完成异常类再回来编译。

同时请思考:Score 类用到了哪些数据类型和运算符?(double 成绩、|| 逻辑或、比较运算符等)

1.7 模块一自测(多态 + 抽象类 + 接口综合验证)

在 main 包下写一个临时测试类 EntityTest(只用于验证,不属于最终系统),完成以下验证:

// EntityTest.java(自测用)
public class EntityTest {
    public static void main(String[] args) throws Exception {
        // 1. 多态:Person[] 数组同时装 Student 和 Teacher
        Person[] people = new Person[2];
        people[0] = new Student("张三", 18, "S001", "Java2301");
        people[1] = new Teacher("王老师", 35, "T001", new Course("C001", "JavaSE 基础", 3));

        // 2. 遍历数组,调用 getRole()(动态绑定:编译看左,运行看右)
        //    预期输出:张三 是 学生 / 王老师 是 教师

        // 3. instanceof 向下转型
        //    如果是 Student → 调用 study();如果是 Teacher → 调用 teach()
    }
}

预期输出:

张三(18 岁)的角色:学生
王老师(35 岁)的角色:教师
张三 同学正在努力学习...
王老师 老师正在讲授《JavaSE 基础》...

模块一思考题(选做,不要求提交):

  1. Person 为什么设计成抽象类?如果不抽象,new Person("李四", 20) 合不合理?(提示:世界上没有"人"这个具体对象)
  2. Student 同时继承 Person 和实现 Studyable,这体现了"单继承多实现"的什么特点?
  3. equals/hashCode 为什么要以学号为判定依据?

模块二:自定义异常体系(⭐ 必做)

知识点:自定义异常、继承 Exception、throw 抛出、throws 声明、try-catch、程序鲁棒性

包名:exception

2.1 定义四个自定义异常

均继承 Exception(编译期异常),提供无参构造 + 带消息构造:

// ScoreOutOfBoundsException.java —— 成绩越界(0~100 之外)
public class ScoreOutOfBoundsException extends Exception {
    public ScoreOutOfBoundsException() { }
    public ScoreOutOfBoundsException(String message) { super(message); }
}

// StudentNotFoundException.java —— 按学号查找学生时不存在
public class StudentNotFoundException extends Exception {
    public StudentNotFoundException() { }
    public StudentNotFoundException(String message) { super(message); }
}

// CourseNotFoundException.java —— 按课程编号查找课程时不存在
public class CourseNotFoundException extends Exception {
    public CourseNotFoundException() { }
    public CourseNotFoundException(String message) { super(message); }
}

// DuplicateStudentException.java —— 添加学生时学号已存在(重复)
public class DuplicateStudentException extends Exception {
    public DuplicateStudentException() { }
    public DuplicateStudentException(String message) { super(message); }
}

2.2 模块二自测:异常处理综合演练

在 main 包下写临时测试类 ExceptionTest,验证异常机制:

// ExceptionTest.java(自测用)
public class ExceptionTest {
    public static void main(String[] args) {
        // 1. 正常成绩:new Score("S001", "C001", 88.5) → 输出成绩信息
        // 2. 非法成绩:new Score("S001", "C001", 150) → 抛出 ScoreOutOfBoundsException
        //    用 try-catch 捕获并输出错误消息:"成绩必须在 0~100 之间,当前值:150.0"
        // 3. 同时抛多个异常(如 -5 和 120),验证多 catch 块各自独立捕获
        // 4. 在 catch 后加 finally 块,输出"—— 成绩录入完成"
    }
}

预期输出:

========== 测试 1:正常成绩 ==========
S001 同学 C001 课程成绩:88.5 分
========== 测试 2:成绩越界 ==========
成绩必须在 0~100 之间,当前值:150.0
—— 成绩录入完成
========== 测试 3:多异常捕获 ==========
成绩必须在 0~100 之间,当前值:-5.0
成绩必须在 0~100 之间,当前值:120.0

模块二思考题(选做):

  1. 自定义异常为什么要继承 Exception 而不是直接继承 Object?
  2. throw 和 throws 的区别是什么?
  3. 多 catch 块的顺序有什么要求?(提示:子类异常在前,父类异常在后)

模块三:系统核心功能(⭐⭐ 必做)

知识点:集合框架(ArrayList / HashMap)、泛型、迭代器、Scanner 键盘输入、流程控制(switch / while / if-else / for)、多态、自定义异常综合应用

包名:manager(管理类)、main(主菜单)

3.1 StudentManager 学生管理类

内部用 ArrayList<Student> 保存所有学生,封装如下功能(方法签名仅供参考,可自行设计):

// StudentManager.java
import java.util.ArrayList;
import java.util.Iterator;

public class StudentManager {
    private ArrayList<Student> students;   // 泛型集合

    public StudentManager() {
        this.students = new ArrayList<>();
    }

    // TODO: addStudent(Student s) —— 添加学生
    //   要求:先判断学号是否已存在(遍历 students 用 equals / id 比对)
    //         已存在 → 抛 DuplicateStudentException("学号已存在:S001")
    //         不存在 → students.add(s),输出"添加成功"

    // TODO: removeById(String id) —— 按学号删除学生
    //   要求:找到并删除;找不到 → 抛 StudentNotFoundException("学生不存在:S001")

    // TODO: findById(String id) —— 按学号查找,返回 Student;找不到返回 null
    //   要求:必须使用迭代器 Iterator 遍历(复习迭代器知识点)

    // TODO: printAll() —— 遍历输出所有学生(foreach 或迭代器)

    // TODO: getStudents() —— 返回学生集合(供排序模块使用)
}

💡 建议(不强制):可以加一个 updateStudent(String id, String newName, int newAge) 修改方法(先 findById,再 setName / setAge),进一步练习"查改"流程。

3.2 CourseManager 课程管理类

内部用 ArrayList<Course> 保存所有课程,封装类似功能:

// CourseManager.java
public class CourseManager {
    private ArrayList<Course> courses;

    public CourseManager() {
        this.courses = new ArrayList<>();
    }

    // TODO: addCourse(Course c) —— 添加课程(课程编号重复则不允许)
    
    // TODO: findById(String courseId) —— 按课程编号查找,返回 Course;找不到抛 CourseNotFoundException
    
    // TODO: removeById(String courseId) —— 按课程编号删除(找不到抛 CourseNotFoundException)
    
    // TODO: printAll() —— 遍历输出所有课程
    
    // TODO: getCourses()
    
}

3.3 ScoreManager 成绩管理类

内部用 ArrayList<Score> 保存"成绩台账"(所有成绩),同时提供按学号分组统计功能:

// ScoreManager.java
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class ScoreManager {
    private ArrayList<Score> scores;                    // 成绩台账
    private HashMap<String, List<Score>> scoreMap;      // 学号 → 该学生的成绩列表(Map 知识点)

    public ScoreManager() {
        this.scores = new ArrayList<>();
        this.scoreMap = new HashMap<>();
    }

    // TODO: addScore(Student stu, Course cou, double sc) —— 录入一条成绩
    //   要求:
    //   1) new Score(stu.getId(), cou.getCourseId(), sc) —— 成绩越界会抛 ScoreOutOfBoundsException
    //   2) 加入全局成绩台账 scores.add(score)
    //   3) 学生侧记录成绩:stu.addScore(score)
    //   4) 更新 scoreMap:按学号分组(若 map 中还没有该学号,先 put 一个新的 ArrayList,再 add)
    //   5) 输出"录入成功:张三 的 JavaSE 基础 成绩 = 88.5"

    // TODO: printAll() —— 遍历输出成绩台账(迭代器或 foreach)

    // TODO: getStudentTotal(String studentId) —— 从 scoreMap 取该学生成绩列表求总分
    // TODO: getStudentAvg(String studentId)   —— 求平均分
    // TODO: countPass() —— 统计及格(>= 60)人数 / 及格率(用运算符计算百分比,保留两位小数)
}

📌 必做点:HashMap<String, List<Score>> 的"按学号分组"必须实现——这是对 Map 集合 和 ArrayList 嵌套 的综合训练。

3.4 StudentSystemApp 主菜单(流程控制)

在 main 包编写程序入口,用 Scanner + switch + while 实现控制台菜单:

// StudentSystemApp.java
import java.util.Scanner;

public class StudentSystemApp {
    public static void main(String[] args) {
        // 1. 创建各管理类对象
        //    StudentManager sm = new StudentManager();
        //    CourseManager  cm = new CourseManager();
        //    ScoreManager   scm = new ScoreManager();

        // 2. 初始化少量示例数据(方便演示):
        //    学生:张三/18/S001/Java2301、李四/19/S002/Java2301、王五/20/S003/Java2302
        //    课程:C001-JavaSE 基础-3、C002-MySQL 数据库-2、C003-数据结构-3
        //    教师:王老师/35/T001/C001

        // 3. 主循环 + 主菜单(while + switch)
        //    主菜单:
        //    ===== 学生信息管理系统 =====
        //    1. 学生管理
        //    2. 课程管理
        //    3. 成绩管理
        //    4. 成绩排名
        //    5. 高级查询(挑战·选做)
        //    0. 退出
        //    请输入你的选择:

        // 4. 二级菜单(以学生管理为例):
        //    ===== 学生管理 =====
        //    1. 添加学生
        //    2. 删除学生
        //    3. 查找学生
        //    4. 查看所有学生
        //    0. 返回上级

        // 5. 每个功能都用 Scanner 接收键盘输入,并 try-catch 处理自定义异常
        //    (例如:添加重复学号 → 捕获 DuplicateStudentException 并提示)
    }
}

菜单交互示例(节选)

===== 学生信息管理系统 =====
1. 学生管理
2. 课程管理
3. 成绩管理
4. 成绩排名
5. 高级查询(挑战·选做)
0. 退出
请输入你的选择:1

===== 学生管理 =====
1. 添加学生
2. 删除学生
3. 查找学生
4. 查看所有学生
0. 返回上级
请输入你的选择:1
请输入姓名:赵六
请输入年龄:21
请输入学号:S004
请输入班级:Java2302
添加成功:赵六

异常处理要求(必做)

在"添加学生""删除学生""录入成绩"三个功能中,必须用 try-catch 处理自定义异常:

操作 可能抛出的异常 提示
添加学生(学号重复) DuplicateStudentException 输出"学号已存在:S004"
删除/查找学生(不存在) StudentNotFoundException 输出"学生不存在:S999"
查找课程(不存在) CourseNotFoundException 输出"课程不存在:C999"
录入成绩(0~100 之外) ScoreOutOfBoundsException 输出"成绩必须在 0~100 之间,当前值:150.0"

模块三思考题(选做):

  1. 用 ArrayList 存储学生和用之前学的 Student[] 数组存储,最大的区别是什么?
  2. 为什么"查找学生"要推荐用迭代器?(提示:遍历时删除元素避免 ConcurrentModificationException)
  3. HashMap<String, List<Score>> 中,key 为什么用学号 String 而不是 Student 对象?(提示:简化 + 唯一)

模块四:排序与排名(⭐⭐ 必做)

知识点:Comparable 自然排序、Comparator 比较器(匿名内部类 / Lambda)、Collections 工具类、TreeSet 自动排序去重、迭代器遍历、compareTo 与 equals/hashCode 一致性

包名:util

4.1 需求说明

在 RankUtil 中完成 4 个排序/排名功能,并在 main 包中写 RankTest 验证:

// RankUtil.java
import java.util.*;

public class RankUtil {
    /**
     * 功能 1:成绩排行榜 —— TreeSet 自然排序
     * 利用 Student 的 compareTo(平均分降序,相同按学号),
     * 把学生添加进 TreeSet<Student>,迭代器遍历输出排行榜
     */
    public static void showRank(StudentManager sm) {
        // TODO: 1) 从 sm.getStudents() 拿到 ArrayList<Student>
        //       2) 创建 TreeSet<Student>,把所有学生 add 进去(自动按自然排序排好 + 去重)
        //       3) 用 Iterator 遍历输出,格式:排名-学号-姓名-平均分
    }

    /**
     * 功能 2:按姓名排序 —— Comparator 匿名内部类
     * 使用 Collections.sort(list, new Comparator<Student>() {...}),
     * 匿名内部类中 o1.getName().compareTo(o2.getName())
     */
    public static void sortByName(StudentManager sm) {
        // TODO: 拷贝一份学生列表,Collections.sort + 匿名内部类 Comparator 按姓名升序
        //       遍历输出
    }

    /**
     * 功能 3:按年龄排序 —— Comparator + Lambda
     * 使用 Collections.sort(list, (s1, s2) -> Integer.compare(s1.getAge(), s2.getAge()));
     */
    public static void sortByAge(StudentManager sm) {
        // TODO: 拷贝学生列表,用 Lambda 按年龄升序排序,遍历输出
    }

    /**
     * 功能 4:按平均分降序(和自然排序对比)—— 用 Collections.sort + 匿名内部类
     * 要求:不依赖 Student 的 compareTo,在 Comparator 中自己算平均分(Double.compare 降序)
     */
    public static void sortByAvgDesc(StudentManager sm) {
        // TODO: Double.compare(o2.getAvgScore(), o1.getAvgScore()) 实现降序
    }
}

4.2 验证用例(RankTest)

沿用模块三的示例数据,给部分学生录入成绩后,调用 RankUtil 的 4 个方法,预期输出如下:

========== 功能 1:成绩排行榜(TreeSet 自然排序) ==========
第1名:S001 张三 平均分 90.0
第2名:S002 李四 平均分 85.0
第3名:S003 王五 平均分 78.0
========== 功能 2:按姓名排序(Comparator 匿名内部类) ==========
S001 张三
S002 李四
S003 王五
========== 功能 3:按年龄排序(Comparator + Lambda) ==========
S001 张三 18 岁
S002 李四 19 岁
S003 王五 20 岁
========== 功能 4:按平均分降序(Collections.sort) ==========
S001 张三 90.0
S002 李四 85.0
S003 王五 78.0

📌 以上输出仅为示意,实际内容取决于你录入的数据,但排序规则必须正确。

4.3 必答原理分析(写在代码注释或提交说明中)

  1. Student 重写 compareTo 后,放入 TreeSet 会发生什么?(提示:自动排序 + 按 compareTo 返回值去重)
  2. 为什么 equals/hashCode 以学号判定,compareTo 也要保证学号相同返回 0?(提示:compareTo 与 equals/hashCode 判定口径一致性,官方推荐 compareTo == 0 → equals 返回 true)
  3. 临时按姓名排序时,为什么不修改 Student 类?(提示:用 Comparator 在集合这边定义排序规则——Collections.sort(list, comparator) 或 TreeSet(comparator))

模块四思考题(选做):

  1. Collections.sort(list) 和 TreeSet 自动排序有什么区别?(提示:前者改已有 List,后者存入时排序+去重)
  2. 功能 3 的 Lambda (s1, s2) -> Integer.compare(...) 等价于哪个方法引用写法?(提示:Comparator.comparingInt(Student::getAge))
  3. 如果只按平均分排序(不二次比较学号),两个平均分相同的学生能同时存入 TreeSet 吗?

模块五:函数式编程与 Stream 流高级查询(⭐⭐⭐⭐ 挑战·选做)

知识点:Lambda 表达式、函数式接口(Predicate / Consumer / Function)、Stream 流(filter / map / count / distinct / sorted / collect)、方法引用

说明:本模块为选做挑战,给出实现建议,不强制要求。学有余力的同学建议完成,它是对今日(0804)所学的 Stream 流与函数式编程 的实战检验。

5.1 需求描述

在 util 包新增 StreamQuery 类,用 Stream 流完成 6 个高级查询(每个查询都是一条 Stream 链):

// StreamQuery.java
import java.util.*;
import java.util.stream.Collectors;

public class StreamQuery {

    /**
     * 查询 1:及格学生名单 —— filter + forEach
     * 过滤出平均分 >= 60 的学生,输出"学号-姓名-平均分"
     * 建议:list.stream().filter(s -> s.getAvgScore() >= 60).forEach(s -> System.out.println(...));
     */
    public static void queryPass(List<Student> list) { }

    /**
     * 查询 2:所有学生姓名 —— map + collect(toList)
     * 把学生流映射成姓名流,收集成 List<String> 输出
     * 建议:list.stream().map(Student::getName).collect(Collectors.toList())
     */
    public static void queryNames(List<Student> list) { }

    /**
     * 查询 3:及格人数统计 —— filter + count
     * 建议:long cnt = list.stream().filter(s -> s.getAvgScore() >= 60).count();
     */
    public static void countPass(List<Student> list) { }

    /**
     * 查询 4:班级去重 —— map + distinct
     * 取出所有班级名,去重后输出
     * 建议:list.stream().map(Student::getClazz).distinct().forEach(System.out::println);
     */
    public static void distinctClazz(List<Student> list) { }

    /**
     * 查询 5:学号 → 学生 映射 —— collect(Collectors.toMap)
     * 收集成 Map<String, Student>(键=学号,值=学生对象)
     * 建议:Map<String, Student> map = list.stream()
     *           .collect(Collectors.toMap(Student::getId, s -> s));
     */
    public static void toStudentMap(List<Student> list) { }

    /**
     * 查询 6:按平均分降序排名 —— sorted + Comparator.comparingDouble 方法引用
     * 建议:list.stream()
     *           .sorted(Comparator.comparingDouble(Student::getAvgScore).reversed())
     *           .forEach(...);
     */
    public static void rankByStream(List<Student> list) { }
}

5.2 预期效果(示意)

========== 查询 1:及格学生 ==========
S001-张三-90.0
S002-李四-85.0
========== 查询 2:所有学生姓名 ==========
[张三, 李四, 王五]
========== 查询 3:及格人数 ==========
及格人数:2
========== 查询 4:班级去重 ==========
Java2301
Java2302
========== 查询 5:学号→学生 Map ==========
{S001=Student{...}, S002=Student{...}, S003=Student{...}}
========== 查询 6:Stream 排名 ==========
S001 张三 90.0
S002 李四 85.0
S003 王五 78.0

5.3 进阶挑战提示(建议)

  1. 方法引用:Student::getName 等价于 s -> s.getName();System.out::println 等价于 s -> System.out.println(s)。
  2. 分页查询(可选):skip((pageNo - 1) * pageSize).limit(pageSize) 实现成绩排行榜分页。
  3. Lambda 与匿名内部类对比(可选):查询 5 可先写 new Function<Student, String>() {...} 匿名内部类版本,再改成 Lambda 版本,体会简化。
  4. 函数式接口(可选):自定义一个函数式接口(如 @FunctionalInterface interface ScoreFilter { boolean test(Student s); }),把"过滤规则"作为参数传递——体会函数式编程"把函数作为参数"的思想。

简答题(必做)

请用文字说明(可配合示例代码)回答以下问题:

1. 抽象类与接口的区别

从以下维度对比 抽象类 和 接口:

  • 关键字与定义方式
  • 能否有构造方法
  • 成员变量(常量 vs 变量)
  • 成员方法(抽象/普通/默认/静态/私有方法)
  • 继承/实现关系(单继承 vs 多实现)
  • 适用场景("is-a" 关系 vs "has-a 能力"关系)

2. equals / hashCode / compareTo 三者的一致性

  • 重写 equals 为什么必须重写 hashCode?
  • compareTo == 0 与 equals 返回 true 有什么关系?本作业中学生类为什么统一以"学号"判定?

3. Comparable 与 Comparator 的区别

  • 分别作用在哪个位置(类内部 / 类外部)?
  • 什么时候用 Comparable,什么时候用 Comparator?
  • 临时按不同字段排序时应该用哪个?(提示:不用改实体类)

4. 自定义异常与异常处理流程

  • 自定义异常为什么要继承 Exception 或 RuntimeException?两者区别?
  • throw 和 throws 的区别?
  • 项目中"录入成绩越界、查找学生失败"等业务错误,为什么适合用自定义异常而不是返回 null / false?(提示:语义清晰 + 强制调用方处理)

5.(选做加分)Lambda 表达式与匿名内部类的区别

从三个维度回答:

  • 需要的类型(接口 / 抽象类 / 普通类)
  • 使用限制(抽象方法个数)
  • 编译原理(是否产生独立 .class 文件)

📊 完成标准与评分参考

评分维度 达标标准 分值参考
模块一 实体类 抽象类/接口/继承/多态/equals/hashCode 全部正确,能编译运行 25 分
模块二 异常 4 个自定义异常 + try-catch 正确使用 15 分
模块三 核心功能 菜单可运行,增删改查 + 成绩录入校验 + Map 分组全部实现 30 分
模块四 排序排名 TreeSet / Comparator / Collections 三种排序正确 20 分
简答题 表达清楚、要点完整 10 分
模块五 Stream(选做) 6 个查询正确,可额外加分 加分 10 分

✅ 提交清单:全部 Java 源码 + 各模块运行结果截图 + 简答题文档 + 提交目录结构与命名合规。


🧭 做题顺序建议(供参考)

  1. 先写实体类(模块一)→ 再写异常类(模块二)→ 编译通过。
  2. 然后写管理类 + 主菜单(模块三),先打通"添加/查看/删除/成绩录入"流程。
  3. 再补排序排名(模块四),用之前准备好的示例数据验证排序规则。
  4. 最后学有余力再做 Stream 高级查询(模块五)。
  5. 边写边用 git 提交,保持每个模块一个 commit,方便回溯。