# 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` 接口、`compareTo(T o)` 重写、返回值规则(负数存左 / 0 重复不存 / 正数存右)、**二次比较**、`TreeSet` 自动排序 + 去重 **场景描述**:编写学生成绩排行榜。`Student` 类实现 `Comparable`(**按成绩降序**,成绩相同再按姓名),存入 `TreeSet` 后**自动排序**并**自动去重**。 **题目要求**: 1. 定义 `Student` 类(`name`、`age`、`score`),**实现 `Comparable`**,重写 `compareTo`: ```java // Student.java public class Student implements Comparable { 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(不传 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,添加 5 个商品: // 苹果 3.5 100 / 香蕉 2.0 200 / 西瓜 6.8 50 / 葡萄 12.5 80 / 橙子 5.0 150 // 2. 匿名内部类 Comparator:按价格升序排序 // 提示:Collections.sort(list, new Comparator() { // 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() {...}); // 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 { 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(自然排序) // 添加 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 文件**,字节码在**运行期动态生成**