Procházet zdrojové kódy

feat: 新增20260803课后作业——比较器排序/Collections工具类/可变参数/Lambda表达式(基础3题+进阶2题+简答题:Comparable学生成绩排行榜、Comparator商品排序、可变参数成绩统计、Lambda简化接口与排序、员工信息管理综合,含包名指定与提交目录结构)

WanJL před 3 týdny
rodič
revize
8c656570c2
1 změnil soubory, kde provedl 489 přidání a 0 odebrání
  1. 489 0
      课后作业/2026年8月3日-作业.md

+ 489 - 0
课后作业/2026年8月3日-作业.md

@@ -0,0 +1,489 @@
+# 2026年8月3日 课后作业 — 比较器排序 / Collections 工具类 / 可变参数 / Lambda 表达式
+
+> **说明**:本次作业围绕今日授课核心内容——**比较器排序(Comparable 自然排序 / Comparator 比较器排序)**、**集合工具类 Collections**、**可变参数 Varargs** 与 **Lambda 表达式(函数式接口)** 展开,涵盖 compareTo/compare 返回值规则、二次比较(多字段排序)、Collections.sort + 匿名内部类、Double.compare 降序技巧、可变参数本质与置末规则、Lambda 标准格式与三条省略规则、compareTo 与 equals/hashCode 一致性等知识点,并附 1 道简答题(Lambda vs 匿名内部类)。
+>
+> 题目分为 **⭐ 基础(必做)**:作业 1~3;**⭐⭐ 进阶(必做)**:作业 4~5;**简答题(必做)**。请新建 Java 项目,编写并运行代码,将运行结果截图提交。
+
+---
+
+## 📌 本次作业重要说明
+
+### 命名要求
+
+| 项目 | 要求 |
+|------|------|
+| **包名** | **必须按题目指定的包名创建**(全小写) |
+| **类名** | 自由命名,但需**见名知意** |
+| **测试类** | 建议命名为 `TestXxx` 或 `XxxTest` |
+| **文件名** | 必须与类名一致 |
+
+### 提交目录结构示例
+
+```
+提交\
+└─ 张三\
+    ├─ p1_comparable\          ← 第 1 题的包(Comparable 自然排序)
+    │   ├─ Student.java
+    │   └─ ScoreRankingTest.java
+    ├─ p2_comparator\          ← 第 2 题的包(Comparator + Collections 工具类)
+    │   ├─ Product.java
+    │   └─ ProductSortDemo.java
+    ├─ p3_varargs\             ← 第 3 题的包(可变参数)
+    │   ├─ Calculator.java
+    │   └─ ScoreCalculator.java
+    ├─ p4_lambda\              ← 第 4 题的包(Lambda 表达式)
+    │   ├─ Addable.java
+    │   ├─ Flyable.java
+    │   └─ LambdaDemo.java
+    └─ p5_employee\            ← 第 5 题的包(综合应用)
+        ├─ Employee.java
+        └─ EmployeeSystem.java
+```
+
+> ⚠ **注意**:
+> 1. 包名必须严格按题目要求书写(全小写),目录结构必须与包结构一致
+> 2. 每个 Java 文件头部需包含 `@author`、`@title`、`@create` 文档注释
+> 3. 简答题(Lambda 与匿名内部类)请写在 Word / TXT 文档中,与代码一起提交
+
+---
+
+# 第一部分:基础题(⭐ 必做)
+
+---
+
+## 作业 1:Comparable 自然排序 — 学生成绩排行榜
+
+**包名**:`p1_comparable`
+
+**知识点**:`Comparable<T>` 接口、`compareTo(T o)` 重写、返回值规则(负数存左 / 0 重复不存 / 正数存右)、**二次比较**、`TreeSet` 自动排序 + 去重
+
+**场景描述**:编写学生成绩排行榜。`Student` 类实现 `Comparable`(**按成绩降序**,成绩相同再按姓名),存入 `TreeSet` 后**自动排序**并**自动去重**。
+
+**题目要求**:
+
+1. 定义 `Student` 类(`name`、`age`、`score`),**实现 `Comparable<Student>`**,重写 `compareTo`:
+
+```java
+// Student.java
+public class Student implements Comparable<Student> {
+    private String name;
+    private int age;
+    private double score;
+
+    public Student(String name, int age, double score) {
+        this.name = name;
+        this.age = age;
+        this.score = score;
+    }
+
+    // TODO: 补全 getter / setter / toString 方法
+
+    // TODO: 重写 compareTo —— 按成绩降序,成绩相同再按姓名
+    @Override
+    public int compareTo(Student o) {
+        // 提示:int result = Double.compare(o.getScore(), this.score);  // 降序
+        //       return result == 0 ? this.name.compareTo(o.getName()) : result;
+    }
+}
+```
+
+2. 编写测试类 `ScoreRankingTest`:
+
+```java
+import java.util.TreeSet;
+
+public class ScoreRankingTest {
+    public static void main(String[] args) {
+        // 1. 创建 TreeSet<Student>(不传 Comparator → 使用自然排序)
+        // 2. 添加 5 名学生:
+        //    张三 18 90 / 李四 19 85 / 王五 20 95 / 赵六 18 90 / 孙七 21 88
+        //    注意:张三和赵六【成绩相同】→ 应触发姓名二次比较,两人都能存入
+        // 3. foreach 遍历输出,观察:成绩降序、同分按姓名排序
+        // 4. 再添加一名"刘八 22 90"(与张三成绩相同),观察去重行为
+    }
+}
+```
+
+**预期输出**:
+```
+========== 学生成绩排行榜 ==========
+王五 20 95.0
+张三 18 90.0
+赵六 18 90.0      ← 与张三同分 → 按姓名二次比较(张 < 赵),都能存入
+孙七 21 88.0
+李四 19 85.0
+```
+
+**思考题**(选做,不要求提交代码):
+1. 返回值负数 / 0 / 正数分别代表什么?存入 TreeSet 时如何处理?
+2. 为什么张三和赵六成绩相同(90)却都能存入?—— `compareTo` 做了二次比较(姓名),返回值不为 0
+3. 如果 `compareTo` 只按成绩比较,会出现什么现象?(提示:同分视为重复,后一个不存)
+
+---
+
+## 作业 2:Comparator 比较器排序 + Collections 工具类 — 商品价格排序
+
+**包名**:`p2_comparator`
+
+**知识点**:`Collections.sort(list, comparator)`、**匿名内部类**实现 Comparator、`Double.compare`、降序技巧(交换参数)、`Collections.max/min/binarySearch`
+
+**场景描述**:`Product` 类(名称 + 价格 + 库存)**不实现** `Comparable`。用 **Collections.sort + 匿名内部类 Comparator** 对商品进行多种临时排序,并练习 Collections 工具类的查找方法。
+
+**题目要求**:
+
+1. 定义 `Product` 类(`name`、`price`、`stock`,补全 getter/setter/toString):
+
+```java
+// Product.java
+public class Product {
+    private String name;
+    private double price;
+    private int stock;
+
+    public Product(String name, double price, int stock) {
+        this.name = name;
+        this.price = price;
+        this.stock = stock;
+    }
+
+    // TODO: 补全 getter / setter / toString 方法
+}
+```
+
+2. 编写 `ProductSortDemo`:
+
+```java
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+public class ProductSortDemo {
+    public static void main(String[] args) {
+        // 1. 创建 ArrayList<Product>,添加 5 个商品:
+        //    苹果 3.5 100 / 香蕉 2.0 200 / 西瓜 6.8 50 / 葡萄 12.5 80 / 橙子 5.0 150
+
+        // 2. 匿名内部类 Comparator:按价格升序排序
+        //    提示:Collections.sort(list, new Comparator<Product>() {
+        //              public int compare(Product p1, Product p2) {
+        //                  return Double.compare(p1.getPrice(), p2.getPrice());
+        //              }
+        //          });
+        //    遍历输出
+
+        // 3. 匿名内部类 Comparator:按价格【降序】排序(交换参数位置)
+        //    遍历输出
+
+        // 4. 匿名内部类 Comparator:按库存升序排序(int 直接相减)
+        //    遍历输出
+
+        // 5. 用 Collections.max(list, comparator) 找出价格最高的商品
+        //    Collections.min(list, comparator) 找出价格最低的商品
+
+        // 6. 用 Collections.binarySearch(list, 目标商品, comparator) 查找(先按价格排序再查找)
+    }
+}
+```
+
+**预期输出**(节选):
+```
+========== 按价格升序 ==========
+香蕉 2.0 200
+苹果 3.5 100
+橙子 5.0 150
+西瓜 6.8 50
+葡萄 12.5 80
+========== 按价格降序 ==========
+葡萄 12.5 80
+西瓜 6.8 50
+橙子 5.0 150
+苹果 3.5 100
+香蕉 2.0 200
+========== 按库存升序 ==========
+西瓜 6.8 50
+葡萄 12.5 80
+苹果 3.5 100
+橙子 5.0 150
+香蕉 2.0 200
+价格最高的商品:葡萄 12.5
+价格最低的商品:香蕉 2.0
+```
+
+**思考题**(选做,不要求提交代码):
+1. double 类型的价格为什么不能用 `(int)(p1.getPrice() - p2.getPrice())`?(提示:浮点精度 + 强转截断)
+2. 想让 Comparator 排序方向反过来,最简单的做法是什么?(提示:交换 compare 中两个参数的位置)
+3. `Collections.binarySearch` 使用前提是什么?(提示:查找前必须先按相同规则排序)
+
+---
+
+## 作业 3:可变参数 Varargs — 任意个数成绩统计
+
+**包名**:`p3_varargs`
+
+**知识点**:可变参数 `int... a`、`for each` 遍历、**可变参数本质即数组**、**固定参数 + 可变参数(可变参数置末)**、求总分/平均分/最高分
+
+**场景描述**:用**可变参数**实现"任意个数成绩的统计"——一个方法接收 2 个、3 个、5 个...任意个数的成绩,并计算总分、平均分、最高分。
+
+**题目要求**:
+
+1. 定义 `Calculator` 类,实现 `add(int... a)` 任意个数整数求和:
+
+```java
+// Calculator.java
+public class Calculator {
+    // TODO: 可变参数方法 add(int... a),累加求和
+    //   提示:int sum = 0;
+    //         for (int n : a) { sum += n; }
+    //         return sum;
+    public int add(int... a) {
+        return 0;  // 请补全
+    }
+}
+```
+
+2. 定义 `ScoreCalculator` 类,实现"固定参数 + 可变参数"的成绩统计:
+
+```java
+// ScoreCalculator.java
+public class ScoreCalculator {
+    // TODO: showScore(String name, int... scores)
+    //   1) 输出学生姓名
+    //   2) 遍历 scores 求总分(total)和最高分(max)
+    //   3) 输出各科成绩、总分、平均分(total * 1.0 / scores.length)
+
+    public static void main(String[] args) {
+        Calculator calc = new Calculator();
+
+        // 1. 同一个 add 方法,传入不同个数的参数:
+        //    add(1, 2) → 3
+        //    add(1, 2, 3) → 6
+        //    add(1, 2, 3, 4, 5) → 15
+
+        // 2. 验证"可变参数本质就是数组":用 int[] 调用 add(arr)
+
+        ScoreCalculator sc = new ScoreCalculator();
+        // 3. 张三 3 门课:85, 90, 78
+        // 4. 李四 5 门课:92, 88, 95, 80, 90
+    }
+}
+```
+
+**预期输出**:
+```
+add(1, 2) = 3
+add(1, 2, 3) = 6
+add(1, 2, 3, 4, 5) = 15
+用数组调用 add(arr) = 15
+========== 张三的成绩单 ==========
+姓名:张三
+各科成绩:85, 90, 78
+总分:253,平均分:84.33,最高分:90
+========== 李四的成绩单 ==========
+姓名:李四
+各科成绩:92, 88, 95, 80, 90
+总分:445,平均分:89.0,最高分:95
+```
+
+**思考题**(选做,不要求提交代码):
+1. `int... a` 和 `int[] a` 是什么关系?为什么调用效果一样?(提示:可变参数是数组的语法糖)
+2. `showScore(String name, int... scores)` 中可变参数能写在 name 前面吗?(提示:可变参数必须放在参数列表最后)
+3. 如果调用 `showScore("王五")`(不传成绩),`scores.length` 是多少?会不会报错?
+
+---
+
+# 第二部分:进阶题(⭐⭐ 必做)
+
+---
+
+## 作业 4:Lambda 表达式 — 简化接口实现与排序
+
+**包名**:`p4_lambda`
+
+**知识点**:**函数式接口**、Lambda 标准格式 `(参数)->{代码块}`、**三条省略规则**(类型可全省 / 单参数省小括号 / 单语句省大括号分号 return)、`Collections.sort` + Lambda
+
+**场景描述**:今日课上新学了 Lambda 表达式——它是**另一种形式的匿名内部类**,能大幅简化代码。要求用 Lambda 实现两个函数式接口,并用 Lambda 改写 `Collections.sort` 的排序代码,体会省略模式的便捷。
+
+**题目要求**:
+
+1. 定义两个函数式接口:
+
+```java
+// Addable.java —— 有参有返回值
+public interface Addable {
+    int add(int x, int y);
+}
+
+// Flyable.java —— 有参无返回值
+public interface Flyable {
+    void fly(String s);
+}
+```
+
+2. 编写 `LambdaDemo`:
+
+```java
+public class LambdaDemo {
+    public static void useAdd(Addable a) {
+        System.out.println(a.add(10, 20));
+    }
+
+    public static void useFlyable(Flyable f) {
+        f.fly("今天天气不错...是风和日丽的...");
+    }
+
+    public static void main(String[] args) {
+        // 1. Addable:完整写法 useAdd((a, b) -> { return a + b; });
+        //    省略写法:代码块只有一条语句 → 省略大括号、分号、return
+        useAdd((a, b) -> a + b);
+
+        // 2. Flyable:参数有且只有一个 → 小括号可以省略
+        useFlyable(s -> {
+            System.out.println(s);
+            System.out.println("长翅膀....飞...");
+        });
+
+        // 3. 用 Lambda 改写 Collections.sort:定义学生集合,按成绩升序排序
+        //    对比匿名内部类写法:
+        //    Collections.sort(list, new Comparator<Student>() {...});
+        //    Lambda 写法:Collections.sort(list, (s1, s2) -> ...);
+    }
+}
+```
+
+3. **进阶挑战**:定义一个 `Product` 类(名称 + 价格),用 **Lambda + Collections.sort** 按价格降序排序输出(提示:`Double.compare(s2.getPrice(), s1.getPrice())`)。
+
+**预期输出**:
+```
+30
+今天天气不错...是风和日丽的...
+长翅膀....飞...
+========== Collections.sort 用 Lambda 排序 ==========
+(按你的学生/商品数据排序输出)
+```
+
+**思考题**(选做,不要求提交代码):
+1. Lambda 表达式的三要素是什么?三条省略规则分别是什么?
+2. 为什么 Lambda 只能用在**只有一个抽象方法**的接口上?(提示:函数式接口,`()` 才能唯一确定指向它)
+3. Lambda 与匿名内部类的本质区别是什么?(提示:编译期是否产生独立 .class 文件)
+
+---
+
+## 作业 5:综合应用 — 员工信息管理系统
+
+**包名**:`p5_employee`
+
+**知识点**:**Comparable 自然排序** + **equals/hashCode 一致性** + `TreeSet` 自动排序/去重 + **匿名内部类或 Lambda 按姓名排序** + 迭代器遍历
+
+**场景描述**:某公司员工信息管理系统。`Employee` 类(工号、姓名、薪资、部门)要求:
+- **默认排序**:按薪资降序,薪资相同再按工号(自然排序)
+- 存入 TreeSet 后**自动排序**且**自动去重**(工号相同的员工视为同一人 → equals/hashCode 与 compareTo 判定口径一致)
+- 临时需求:按姓名排序输出一份通讯录(**不修改 Employee 类**,用 Comparator 匿名内部类或 Lambda)
+
+**题目要求**:
+
+1. 定义 `Employee` 类:
+
+```java
+import java.util.Objects;
+
+public class Employee implements Comparable<Employee> {
+    private String id;      // 工号
+    private String name;    // 姓名
+    private double salary;  // 薪资
+    private String dept;    // 部门
+
+    public Employee(String id, String name, double salary, String dept) {
+        this.id = id;
+        this.name = name;
+        this.salary = salary;
+        this.dept = dept;
+    }
+
+    // TODO: 补全 getter / setter / toString 方法
+
+    // TODO: 重写 compareTo —— 按薪资降序,薪资相同再按工号
+    @Override
+    public int compareTo(Employee o) {
+        // 提示:int result = Double.compare(o.getSalary(), this.salary);  // 降序
+        //       return result == 0 ? this.id.compareTo(o.getId()) : result;
+    }
+
+    // TODO: 重写 equals / hashCode —— 与 compareTo 判定口径一致(以工号判定)
+    //   提示:equals 用 Objects.equals(id, employee.id);hashCode 用 Objects.hash(id)
+}
+```
+
+2. 编写 `EmployeeSystem`:
+
+```java
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.TreeSet;
+
+public class EmployeeSystem {
+    public static void main(String[] args) {
+        // 1. 创建 TreeSet<Employee>(自然排序)
+        //    添加 5 名员工(其中两名工号相同,测试去重):
+        //    E001-张三-12000-技术部 / E002-李四-15000-市场部
+        //    E003-王五-15000-技术部 / E004-赵六-9000-财务部
+        //    E005-刘七-12000-市场部 / 再添加一个工号重复的 E002-李四-20000-市场部
+
+        // 2. 迭代器遍历输出 → 观察:按薪资降序,薪资相同按工号;工号重复的自动去重
+        //    输出格式:"E001-张三-12000.0-技术部"
+
+        // 3. 通讯录按姓名排序:重新创建 TreeSet,传入按姓名排序的 Comparator
+        //    (可用匿名内部类,也可尝试用 Lambda:Comparator.comparing(Employee::getName) 或
+        //    (o1, o2) -> o1.getName().compareTo(o2.getName()))
+        //    迭代器遍历输出
+    }
+}
+```
+
+**预期输出**:
+```
+========== 默认排序:按薪资降序(薪资相同按工号) ==========
+E002-李四-15000.0-市场部
+E003-王五-15000.0-技术部
+E001-张三-12000.0-技术部
+E005-刘七-12000.0-市场部
+E004-赵六-9000.0-财务部
+========== 通讯录:按姓名排序 ==========
+E002-李四-15000.0-市场部
+E005-刘七-12000.0-市场部
+E003-王五-15000.0-技术部
+E001-张三-12000.0-技术部
+E004-赵六-9000.0-财务部
+```
+
+**原理分析(必答,写在代码注释或提交说明中)**:
+1. 为什么 E002 添加两次却只存了一个?—— `compareTo` 工号相同返回 0,视为重复,第二次 `add` 不生效
+2. 为什么 `equals` / `hashCode` 要以**工号**为判定依据?—— 与 `compareTo` 判定口径一致,官方推荐 `compareTo == 0 → equals 返回 true、hashCode 一致`
+3. 临时按姓名排序为什么不用修改 Employee 类?—— 用 Comparator(匿名内部类 / Lambda),排序规则在集合这边
+
+**思考题**(选做,不要求提交代码):
+1. `Collections.sort` 和 `TreeSet` 自动排序有什么区别?(提示:前者改变已有 List 顺序,后者存入时排序且去重)
+2. 用 Lambda 重写按姓名排序的 Comparator 怎么写?(提示:`(o1, o2) -> o1.getName().compareTo(o2.getName())`)
+3. 如果 TreeSet 添加的员工**工号和薪资都相同**但姓名不同,能都存入吗?`equals` 会返回 true 吗?(提示:compareTo 返回 0 视为重复不存;equals 以工号判定也会返回 true——口径一致)
+
+---
+
+# 第三部分:简答题(必做)
+
+## 简答题:Lambda 表达式与匿名内部类的区别
+
+请用文字说明(可配合示例代码)回答以下问题:
+
+1. **Lambda 表达式的标准格式和三要素是什么?** `(形式参数)->{代码块}` 各部分分别对应什么?
+2. **Lambda 有哪些省略规则?** 请各举一个例子说明。
+3. **Lambda 和匿名内部类有什么区别?** 请从以下三个维度回答:
+   - 需要的类型(接口 / 抽象类 / 普通类)
+   - 使用限制(抽象方法个数)
+   - 编译原理(是否产生独立的 .class 文件)
+
+**答题要点提示**:
+- 标准格式:`(形式参数) -> {代码块}`;三要素:形式参数、箭头、代码块
+- 三条省略规则:参数类型可全省(不能只省一个);单参数可省小括号;单语句可省大括号/分号/return
+- 区别:
+  - 需要的类型:匿名内部类可以是**接口、抽象类、普通具体类**;Lambda **只能是接口**
+  - 使用限制:接口有多个抽象方法只能用匿名内部类;Lambda 要求**有且只有一个抽象方法**(函数式接口)
+  - 编译原理:匿名内部类编译产生**独立的 .class 文件**;Lambda 编译**不产生 .class 文件**,字节码在**运行期动态生成**
+