# 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)—— 学 → 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` 装 Student+Teacher,`instanceof` 向下转型 | | 自定义异常 | 模块二/三 | 继承 `Exception`、`throw` 抛出、`throws` 声明、多 `catch` + `finally` | | 集合框架 | 模块三 | `ArrayList` 学生/课程/成绩、`HashMap>` 按学号分组 | | 泛型 | 模块三 | `ArrayList` 等泛型集合 | | 迭代器 | 模块三/四 | `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`,作为"人"的公共抽象: ```java // 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 要求定义两个**接口**,分别规范"学生能学习、教师能授课": ```java // 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, Studyable`,核心要求: ```java // Student.java import java.util.ArrayList; import java.util.List; public class Student extends Person implements Comparable, Studyable { private String id; // 学号,如 S001 private String clazz; // 班级,如 "Java2301" private List 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`: ```java // 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 课程类 ```java // 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 成绩类 ```java // 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`(只用于验证,不属于最终系统),完成以下验证: ```java // 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`(编译期异常),提供无参构造 + 带消息构造: ```java // 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`,验证异常机制: ```java // 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` 保存所有学生,封装如下功能(方法签名仅供参考,可自行设计): ```java // StudentManager.java import java.util.ArrayList; import java.util.Iterator; public class StudentManager { private ArrayList 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` 保存所有课程,封装类似功能: ```java // CourseManager.java public class CourseManager { private ArrayList 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` 保存"成绩台账"(所有成绩),同时提供按学号分组统计功能: ```java // ScoreManager.java import java.util.ArrayList; import java.util.HashMap; import java.util.List; public class ScoreManager { private ArrayList scores; // 成绩台账 private HashMap> 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>` 的"按学号分组"必须实现——这是对 **Map 集合** 和 **ArrayList 嵌套** 的综合训练。 ## 3.4 StudentSystemApp 主菜单(流程控制) 在 `main` 包编写程序入口,用 **Scanner + switch + while** 实现控制台菜单: ```java // 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>` 中,key 为什么用学号 String 而不是 Student 对象?(提示:简化 + 唯一) --- # 模块四:排序与排名(⭐⭐ 必做) > **知识点**:`Comparable` 自然排序、`Comparator` 比较器(匿名内部类 / Lambda)、`Collections` 工具类、`TreeSet` 自动排序去重、迭代器遍历、compareTo 与 equals/hashCode 一致性 > > **包名**:`util` ## 4.1 需求说明 在 `RankUtil` 中完成 4 个排序/排名功能,并在 `main` 包中写 `RankTest` 验证: ```java // RankUtil.java import java.util.*; public class RankUtil { /** * 功能 1:成绩排行榜 —— TreeSet 自然排序 * 利用 Student 的 compareTo(平均分降序,相同按学号), * 把学生添加进 TreeSet,迭代器遍历输出排行榜 */ public static void showRank(StudentManager sm) { // TODO: 1) 从 sm.getStudents() 拿到 ArrayList // 2) 创建 TreeSet,把所有学生 add 进去(自动按自然排序排好 + 去重) // 3) 用 Iterator 遍历输出,格式:排名-学号-姓名-平均分 } /** * 功能 2:按姓名排序 —— Comparator 匿名内部类 * 使用 Collections.sort(list, new Comparator() {...}), * 匿名内部类中 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 链**): ```java // 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 list) { } /** * 查询 2:所有学生姓名 —— map + collect(toList) * 把学生流映射成姓名流,收集成 List 输出 * 建议:list.stream().map(Student::getName).collect(Collectors.toList()) */ public static void queryNames(List list) { } /** * 查询 3:及格人数统计 —— filter + count * 建议:long cnt = list.stream().filter(s -> s.getAvgScore() >= 60).count(); */ public static void countPass(List list) { } /** * 查询 4:班级去重 —— map + distinct * 取出所有班级名,去重后输出 * 建议:list.stream().map(Student::getClazz).distinct().forEach(System.out::println); */ public static void distinctClazz(List list) { } /** * 查询 5:学号 → 学生 映射 —— collect(Collectors.toMap) * 收集成 Map(键=学号,值=学生对象) * 建议:Map map = list.stream() * .collect(Collectors.toMap(Student::getId, s -> s)); */ public static void toStudentMap(List list) { } /** * 查询 6:按平均分降序排名 —— sorted + Comparator.comparingDouble 方法引用 * 建议:list.stream() * .sorted(Comparator.comparingDouble(Student::getAvgScore).reversed()) * .forEach(...); */ public static void rankByStream(List 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() {...}` 匿名内部类版本,再改成 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,方便回溯。