# 2026-08-03 比较器排序与可变参数专项练习(课堂练习用) > **说明**:以下习题围绕今日授课核心内容——**比较器排序(Comparable 自然排序 / Comparator 比较器排序)** 与 **可变参数(Varargs)** 展开,涵盖 Comparable 接口实现与 `compareTo(T o)` 重写、Comparator 接口与匿名内部类、TreeSet/TreeMap 有序集合自动排序、返回值规则(负数存左 / 0 重复不存 / 正数存右)、`Collections.sort(list, comparator)`、`Double.compare`、compareTo 与 equals 一致性、可变参数 `形参类型... 参数名`(本质即数组、可变参数必须放最后)等知识点。 > > 题目分为 **基础练习**(Comparable 自然排序 / Comparator 比较器排序 / Collections.sort 排序 / 可变参数,5 题)和 **进阶挑战**(比较器综合应用,2 题)。 --- # 第一部分:基础练习 > 以下练习围绕今日授课核心内容展开,由浅入深。 --- ## 练习 1:Comparable 自然排序(TreeSet 自动排序 + 去重) **难度**:⭐ **知识点**:`Comparable` 接口、`compareTo(T o)` 重写、`TreeSet` 有序 + 去重、返回值规则 **场景描述**:TreeSet 是有序集合,取出的顺序是**排序过**的。不是所有类型都能直接存入 TreeSet——必须实现 `Comparable` 接口(自然排序),或在创建集合时于构造方法中传入 `Comparator`(比较器排序)。本节课 Student 类实现自然排序:**按年龄升序,年龄相同再按姓名排序**。 **题目要求**: 1. 定义 `Student` 类(`name`、`age`、`score` 三个属性),**实现 `Comparable` 接口**,重写 `compareTo(T o)` 方法: ```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 = this.age - o.getAge(); // return result == 0 ? this.name.compareTo(o.getName()) : result; } } ``` 2. 编写测试类 `NaturalSortTest`: ```java import java.util.TreeSet; public class NaturalSortTest { public static void main(String[] args) { // 1. 创建 TreeSet(不传 Comparator → 使用自然排序) // 2. 添加 4 名学生:张三20 / 李四22 / 王五20 / 赵六21(注意:张三和王五年龄相同) // 3. foreach 遍历输出,观察自动排序结果 // 4. 再添加一名"刘七20",观察:刘七能否存入?为什么?(提示:compareTo 返回值规则) } } ``` **预期输出**: ``` 张三 20 85 王五 20 78 ← 年龄 20 与"张三"相同 → 触发姓名二次比较(王 < 张) 赵六 21 88 李四 22 90 ``` **思考题**: 1. 返回值为负数 / 0 / 正数分别代表什么?元素会存入左边、视为重复不存、还是存入右边? 2. 为什么年龄相同的"张三"和"王五"都能存入?—— compareTo 里做了**二次比较**(姓名),返回值不为 0,所以不视为重复 3. 如果 compareTo 只按年龄比较,添加"刘七20"会发生什么?(提示:20 与已有节点比较返回 0 → 视为重复不存) --- ## 练习 2:Comparator 比较器排序(TreeSet 构造传入匿名内部类) **难度**:⭐⭐ **知识点**:`Comparator` 接口、`compare(o1, o2)` 重写、匿名内部类、`TreeSet(Comparator)` 构造方法 **场景描述**:类设计期间已经实现 Comparable(默认按年龄排序),但现在**临时**需要按别的规则排序(如成绩),又**不修改** Student 类。此时使用**比较器排序**——创建集合对象时,在构造方法中传入 `Comparator` 接口的实现类对象(匿名内部类)。 **题目要求**: ```java import java.util.Comparator; import java.util.TreeSet; public class ComparatorSortTest { public static void main(String[] args) { // 1. 创建 TreeSet,构造方法传入匿名内部类 Comparator // 排序规则:按成绩(score)升序,成绩相同再按姓名排序 // 提示:TreeSet set = new TreeSet<>(new Comparator() { // @Override // public int compare(Student o1, Student o2) { // int result = (int)(o1.getScore() - o2.getScore()); // return result == 0 ? o1.getName().compareTo(o2.getName()) : result; // } // }); // 2. 添加 4 名学生:张三85 / 李四90 / 王五78 / 赵六88 // 3. foreach 遍历输出,观察:这次是按【成绩】排序,而不是年龄 // 4. 与练习 1 的输出顺序对比,体会"自然排序 vs 比较器排序" } } ``` **预期输出**: ``` 王五 20 78 张三 7 85 赵六 222 88 李四 2 90 ``` **思考题**: 1. 为什么 `new TreeSet<>(new Comparator(){...})` 就能覆盖默认的自然排序? 2. Comparator 的 `compare(o1, o2)` 和 Comparable 的 `compareTo(o)` 写法有什么不同?(提示:一个是"两个对象在外部比较",一个是"this 与 o 比较") 3. 匿名内部类为什么适合用来传 Comparator?(提示:临时规则、用完即弃、无需新建类文件) --- ## 练习 3:Collections.sort 对 List 排序(按年龄升序 / 按成绩降序) **难度**:⭐⭐ **知识点**:`Collections.sort(list, comparator)`、匿名内部类、`Double.compare(a, b)`、降序技巧(交换参数) **场景描述**:`ArrayList` 是**无序**的,想让它按指定规则排序,用 `Collections.sort(list, comparator)`——传入匿名内部类 Comparator,对 List **原地排序**(改变原集合顺序)。 **题目要求**: ```java import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; public class ListSortTest { public static void main(String[] args) { // 1. 创建 ArrayList,添加 4 名学生:张三20-85 / 李四22-90 / 王五19-78 / 赵六21-88 // 2. 匿名内部类 Comparator:按年龄升序排序(s1.getAge() - s2.getAge()) // Collections.sort(list, new Comparator() {...}); // 遍历输出 // 3. 匿名内部类 Comparator:按成绩(double)降序排序 // 注意:成绩是 double,不能直接相减强转 int // 提示:Double.compare(s2.getScore(), s1.getScore()) ← 注意参数顺序 = 降序 // 遍历输出 } } ``` **预期输出**: ``` ========== 按年龄升序 ========== 王五 19 78 张三 20 85 赵六 21 88 李四 22 90 ========== 按成绩降序 ========== 李四 22 90 赵六 21 88 张三 20 85 王五 19 78 ``` **思考题**: 1. double 类型的成绩为什么不能用 `(int)(s1.getScore() - s2.getScore())`?用 `Double.compare` 有什么好处?(提示:浮点精度 + 强转截断) 2. `Collections.sort(list, comparator)` 和 `TreeSet` 自动排序的区别是什么?(提示:一个改变已有 List 的顺序,一个在存入时排序;TreeSet 还会去重) 3. 想让 Comparator 排序方向反过来,最简单的做法是什么?(提示:交换 compare 中两个参数的位置) --- ## 练习 4:可变参数基础——任意个数整数求和 **难度**:⭐⭐ **知识点**:可变参数 `int... a`、`for each` 遍历、方法重载 vs 可变参数、可变参数本质即数组 **场景描述**:以前计算"多个整数相加",有几个参数就要定义几个**方法重载**(`add(int a, int b)` / `add(int a, int b, int c)`...),写多少个都覆盖不完。有了**可变参数**,一个方法就能接收**任意个数**的参数——**方便用户使用,也方便自己定义**。 **题目要求**: 1. 定义 `Calculator` 类,编写可变参数方法 `add(int... a)`,用 for each 累加求和: ```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; // 请补全 } public static void main(String[] args) { Calculator calc = new Calculator(); // 1. 同一个方法,传入不同个数的参数: // add(1, 2) → 3 // add(1, 2, 3) → 6 // add(1, 2, 3, 4, 5) → 15 // add(1,2,...,10) → 55 // 2. 验证"可变参数本质就是数组":用数组调用,效果与直接传参数一致 int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // calc.add(arr) → 55 } } ``` **预期输出**: ``` add(1, 2) = 3 add(1, 2, 3) = 6 add(1, 2, 3, 4, 5) = 15 add(1~10 累加) = 55 用数组调用 add(arr) = 55 ``` **思考题**: 1. `int... a` 和 `int[] a` 是什么关系?为什么两者调用效果一样?(提示:可变参数是数组的语法糖) 2. 既然有了可变参数,为什么还要方法重载?(提示:参数**个数**不确定用可变参数;参数**个数固定、类型不同**用重载更清晰) 3. 如果把 `add(int... a)` 改成 `add(int[] a)`,调用时还能写 `add(1, 2, 3)` 吗?(提示:不能,数组参数必须显式传数组) --- ## 练习 5:可变参数进阶——学生成绩统计(固定参数 + 可变参数) **难度**:⭐⭐ **知识点**:固定参数与可变参数共存、**可变参数必须放在最后**、求总分/平均分/最大值、可变参数的综合应用 **场景描述**:一个学生有多门课程成绩,成绩**个数不定**。要求:方法同时接收"学生姓名"(固定参数)和"若干门成绩"(可变参数),统计输出总分和平均分。**注意:可变参数必须放在参数列表的最后。** **题目要求**: 1. 定义 `ScoreCalculator` 类,编写方法 `showScore(String name, int... scores)`: ```java public class ScoreCalculator { // TODO: 编写方法 showScore(String name, int... scores) // 1) 输出学生姓名 // 2) 遍历 scores 求和(total)和求最大值(max) // 3) 输出各科成绩、总分、平均分(平均分用 double,total * 1.0 / scores.length) public static void main(String[] args) { ScoreCalculator sc = new ScoreCalculator(); sc.showScore("张三", 85, 90, 78); // 3 门课 sc.showScore("李四", 92, 88, 95, 80, 90); // 5 门课 } } ``` 2. **进阶挑战**:再实现两个可变参数方法,体会通用性: - `max(int... a)`:返回任意个数整数中的最大值 - `join(String separator, String... strings)`:用分隔符拼接若干字符串,如 `join("-", "Java", "SE", "260713")` → `"Java-SE-260713"`(注意:固定参数 separator 在前,可变参数在后) **预期输出**: ``` ========== 张三的成绩单 ========== 姓名:张三 各科成绩:85, 90, 78 总分:253,平均分:84.33 最高分:90 ========== 李四的成绩单 ========== 姓名:李四 各科成绩:92, 88, 95, 80, 90 总分:445,平均分:89.0 最高分:95 ========== 可变参数进阶 ========== max(3, 9, 5, 1) = 9 join("-", "Java", "SE", "260713") = Java-SE-260713 ``` **思考题**: 1. `showScore(String name, int... scores)` 中,可变参数能写在 name 前面吗?为什么?(提示:可变参数必须放在参数列表最后,否则编译报错) 2. 如果调用 `showScore("张三")`(不传成绩),`scores.length` 是多少?会不会报错?(提示:可变参数可传 0 个,scores 是长度为 0 的数组) 3. `join` 方法为什么把分隔符 separator 放在前面、可变参数放后面?如果反过来 `join(String... strings, String separator)` 行吗?(提示:违背"可变参数置末"规则) --- # 第二部分:进阶挑战 > 以下练习综合运用 Comparable / Comparator、TreeMap、compareTo 与 equals 一致性等知识。 --- ## 进阶 1:成绩排行榜(TreeMap + Comparable 键排序) **难度**:⭐⭐⭐ **知识点**:`TreeMap` 按键排序、自定义类型作为 Key、`entrySet()` 遍历、compareTo/equals 一致性 **场景描述**:TreeMap 是**按键排序**的有序 Map。把 `Student` 对象作为 **Key**、班级作为 Value 存入 TreeMap。因为 Student 实现了 Comparable,TreeMap 会自动按 compareTo 的规则对键排序——实现"成绩排行榜"。 **题目要求**: 1. 定义 `Student` 类(实现 Comparable,**按成绩降序**排序,成绩相同按姓名),修改 compareTo: ```java public class Student implements Comparable { private String name; private int age; private double score; // 构造方法、getter/setter、toString(略) @Override public int compareTo(Student o) { // TODO: 按成绩降序(注意:谁减谁决定升降序) // 提示:int result = Double.compare(o.getScore(), this.score); // 降序 // return result == 0 ? this.name.compareTo(o.getName()) : result; } } ``` 2. 编写测试类 `ScoreRankingTest`: ```java import java.util.Map; import java.util.TreeMap; public class ScoreRankingTest { public static void main(String[] args) { // 1. 创建 TreeMap:学生 → 所在班级 // 提示:Map map = new TreeMap<>(); // 添加:张三90-"一班"、李四85-"二班"、王五95-"三班"、赵六88-"一班" // 2. entrySet() 遍历输出,观察是否按成绩从高到低排序 // 输出格式:"王五 95 - 三班" // 3. 思考:如果两个学生成绩相同且姓名相同(属性完全一致),再 put 会发生什么? // —— compareTo 返回 0 → 视为同一个键 → 覆盖旧值(去重) } } ``` **预期输出**: ``` 王五 95 - 三班 张三 90 - 一班 赵六 88 - 一班 李四 85 - 二班 ``` **原理分析(必答)**: 1. TreeMap 是怎么判断两个键是否重复的?—— 调用键的 `compareTo` 方法,返回 0 即视为重复 2. 官方为什么强烈推荐"compareTo == 0 时 equals 返回 true、hashCode 一致"?—— 保证排序判定与相等判定保持一致,避免集合去重行为出现矛盾 3. 如果 Student 没有实现 Comparable 也不传 Comparator,`new TreeMap()` 会怎样?(提示:存入第一个键时抛 ClassCastException) --- ## 进阶 2:员工信息管理系统(多排序规则 + compareTo/equals 一致性 + 综合应用) **难度**:⭐⭐⭐⭐ **知识点**:Comparable 与 Comparator 结合、`hashCode()` / `equals()` 重写、compareTo 与 equals 一致性、多维度排序、迭代器遍历 **场景描述**:某公司开发员工信息管理系统。员工类 `Employee`(工号、姓名、薪资、部门)。要求: - **默认排序**(自然排序):按薪资降序,薪资相同再按工号 - 员工加入 TreeSet 后**自动排序**且**自动去重**(工号相同的员工视为同一人) - 临时需求:按姓名排序输出一份通讯录(使用 Comparator 比较器排序,不修改 Employee 类) **题目要求**: 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) { // 提示:Double.compare(o.getSalary(), this.salary) // 降序 // 若返回 0,再比较工号:this.id.compareTo(o.getId()) } // TODO: 重写 equals / hashCode —— 与 compareTo 保持一致(工号相同即视为同一人) @Override public boolean equals(Object o) { ... } @Override public int hashCode() { ... } } ``` 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-市场部 // 2. 迭代器遍历输出 → 观察:按薪资降序,薪资相同按工号;工号重复的自动去重 // 输出格式:"E001-张三-12000.0-技术部" // 3. 临时需求:通讯录按姓名排序 —— 创建 TreeSet 传入按姓名排序的 Comparator // 匿名内部类:o1.getName().compareTo(o2.getName()) // 迭代器遍历输出 // 4. 思考:E001-张三(12000) 和 E005-刘七(12000) 薪资相同,为何都能存入? // —— compareTo 在薪资相同后继续比较工号,返回值不为 0 } } ``` **预期输出**: ``` ========== 默认排序:按薪资降序(薪资相同按工号) ========== E002-李四-15000.0-市场部 E003-王五-15000.0-技术部 ← 与李四薪资相同 → 按工号 E002 { private String name; private int age; private double score; // 构造方法、getter/setter、toString(略) @Override public int compareTo(Student o) { // 按年龄升序 int result = this.age - o.getAge(); // 年龄相同 → 按姓名升序(二次比较) return result == 0 ? this.name.compareTo(o.getName()) : result; } } // NaturalSortTest TreeSet set = new TreeSet<>(); set.add(new Student("张三", 20, 85)); set.add(new Student("李四", 22, 90)); set.add(new Student("王五", 20, 78)); set.add(new Student("赵六", 21, 88)); for (Student s : set) { System.out.println(s); } // 输出顺序:王五20 → 张三20 → 赵六21 → 李四22(年龄升序,20 岁两人按姓名排序) ``` ## 练习 2 参考 ```java TreeSet set = new TreeSet<>(new Comparator() { @Override public int compare(Student o1, Student o2) { // 按成绩升序,成绩相同再按姓名 int result = (int) (o1.getScore() - o2.getScore()); return result == 0 ? o1.getName().compareTo(o2.getName()) : result; } }); set.add(new Student("张三", 7, 85)); set.add(new Student("李四", 2, 90)); set.add(new Student("王五", 20, 78)); set.add(new Student("赵六", 222, 88)); for (Student s : set) { System.out.println(s); } // 输出顺序:王五78 → 张三85 → 赵六88 → 李四90(成绩升序,与年龄无关) ``` ## 练习 3 参考 ```java ArrayList list = new ArrayList<>(); list.add(new Student("张三", 20, 85)); list.add(new Student("李四", 22, 90)); list.add(new Student("王五", 19, 78)); list.add(new Student("赵六", 21, 88)); // 按年龄升序 Collections.sort(list, new Comparator() { @Override public int compare(Student s1, Student s2) { return s1.getAge() - s2.getAge(); } }); for (Student s : list) { System.out.println(s); } // 按成绩降序(Double.compare + 交换参数 = 降序) Collections.sort(list, new Comparator() { @Override public int compare(Student s1, Student s2) { return Double.compare(s2.getScore(), s1.getScore()); } }); for (Student s : list) { System.out.println(s); } ``` ## 练习 4 参考 ```java public class Calculator { // 可变参数:本质就是 int[] a public int add(int... a) { int sum = 0; for (int n : a) { sum += n; } return sum; } public static void main(String[] args) { Calculator calc = new Calculator(); System.out.println("add(1, 2) = " + calc.add(1, 2)); System.out.println("add(1, 2, 3) = " + calc.add(1, 2, 3)); System.out.println("add(1, 2, 3, 4, 5) = " + calc.add(1, 2, 3, 4, 5)); int sum10 = calc.add(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); System.out.println("add(1~10 累加) = " + sum10); // 可变参数本质就是数组:用数组调用效果一致 int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; System.out.println("用数组调用 add(arr) = " + calc.add(arr)); } } ``` ## 练习 5 参考 ```java public class ScoreCalculator { // 固定参数 name + 可变参数 scores(可变参数必须放最后) public void showScore(String name, int... scores) { System.out.println("========== " + name + "的成绩单 =========="); System.out.println("姓名:" + name); int total = 0; int max = scores[0]; // 假设至少有一门成绩 StringBuilder sb = new StringBuilder(); for (int i = 0; i < scores.length; i++) { total += scores[i]; if (scores[i] > max) { max = scores[i]; } sb.append(scores[i]).append(i == scores.length - 1 ? "" : ", "); } System.out.println("各科成绩:" + sb); System.out.println("总分:" + total + ",平均分:" + String.format("%.2f", total * 1.0 / scores.length)); System.out.println("最高分:" + max); } // 进阶:求任意个数整数的最大值 public int max(int... a) { int max = a[0]; for (int n : a) { if (n > max) max = n; } return max; } // 进阶:用分隔符拼接若干字符串(固定参数在前,可变参数在后) public String join(String separator, String... strings) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < strings.length; i++) { sb.append(strings[i]); if (i != strings.length - 1) { sb.append(separator); } } return sb.toString(); } public static void main(String[] args) { ScoreCalculator sc = new ScoreCalculator(); sc.showScore("张三", 85, 90, 78); sc.showScore("李四", 92, 88, 95, 80, 90); System.out.println("========== 可变参数进阶 =========="); System.out.println("max(3, 9, 5, 1) = " + sc.max(3, 9, 5, 1)); System.out.println("join(\"-\", \"Java\", \"SE\", \"260713\") = " + sc.join("-", "Java", "SE", "260713")); } } ``` ## 进阶 1 参考 ```java // Student.compareTo:按成绩降序,成绩相同按姓名 @Override public int compareTo(Student o) { int result = Double.compare(o.getScore(), this.score); // 降序 return result == 0 ? this.name.compareTo(o.getName()) : result; } // ScoreRankingTest TreeMap map = new TreeMap<>(); map.put(new Student("张三", 18, 90), "一班"); map.put(new Student("李四", 19, 85), "二班"); map.put(new Student("王五", 20, 95), "三班"); map.put(new Student("赵六", 18, 88), "一班"); for (Map.Entry me : map.entrySet()) { System.out.println(me.getKey().getName() + " " + me.getKey().getScore() + " - " + me.getValue()); } // 输出顺序:王五95 → 张三90 → 赵六88 → 李四85(成绩降序) ``` ## 进阶 2 参考(核心逻辑) ```java // Employee.compareTo:按薪资降序,薪资相同按工号 @Override public int compareTo(Employee o) { int result = Double.compare(o.getSalary(), this.salary); // 薪资降序 return result == 0 ? this.id.compareTo(o.getId()) : result; } // equals / hashCode:与 compareTo 一致,以工号判定 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Employee employee = (Employee) o; return Objects.equals(id, employee.id); } @Override public int hashCode() { return Objects.hash(id); } // EmployeeSystem TreeSet set = new TreeSet<>(); set.add(new Employee("E001", "张三", 12000, "技术部")); set.add(new Employee("E002", "李四", 15000, "市场部")); set.add(new Employee("E003", "王五", 15000, "技术部")); set.add(new Employee("E004", "赵六", 9000, "财务部")); set.add(new Employee("E005", "刘七", 12000, "市场部")); Iterator it = set.iterator(); while (it.hasNext()) { System.out.println(it.next()); } // 输出:E002 → E003 → E001 → E005 → E004(薪资降序,同薪按工号) // 通讯录:按姓名排序的临时 Comparator TreeSet byName = new TreeSet<>(new Comparator() { @Override public int compare(Employee o1, Employee o2) { return o1.getName().compareTo(o2.getName()); } }); byName.addAll(set); for (Employee e : byName) { System.out.println(e); } ``` --- # 涵盖知识点总览 | 知识点 | 对应练习 | 说明 | |--------|---------|------| | Comparable 接口与 compareTo 重写 | 练习 1、进阶 1、进阶 2 | 实体类内建默认排序规则(自然排序) | | 返回值规则(负数/0/正数) | 练习 1、练习 2 | 负数存左、0 重复不存、正数存右 | | 二次比较(多字段排序) | 练习 1、进阶 1、进阶 2 | 主排序相同 → 叠加次排序字段,避免误判重复 | | Comparator 接口与匿名内部类 | 练习 2、练习 3 | 临时自定义排序,不改实体类 | | TreeSet(Comparator) 构造方法 | 练习 2 | 构造方法传入比较器 → 覆盖自然排序 | | Collections.sort(list, comparator) | 练习 3 | 对 List 原地排序 | | Double.compare(a, b) | 练习 3、进阶 1、进阶 2 | double 比较的标准做法,避免精度问题 | | 降序技巧(交换参数) | 练习 3、进阶 2 | compare(s2, s1) / compareTo 中交换比较顺序 | | TreeMap 按键排序 | 进阶 1 | 自定义类型作为 Key,按 compareTo 排序并去重 | | compareTo 与 equals/hashCode 一致性 | 进阶 1、进阶 2 | compareTo == 0 → equals 返回 true、hashCode 一致 | | TreeSet 自动排序 + 去重 | 练习 1、进阶 2 | compareTo 返回 0 视为重复,不存入 | | Iterator 迭代器遍历 | 进阶 2 | Set 没有索引,用迭代器统一遍历 | | 可变参数定义与调用 | 练习 4、练习 5 | `int... a` 参数个数可变,一个方法接收任意个数参数 | | 可变参数本质即数组 | 练习 4 | `int... a` 等价于 `int[] a`,可用数组直接调用 | | 方法重载 vs 可变参数 | 练习 4 | 参数个数不确定用可变参数;个数固定、类型不同用重载 | | 固定参数 + 可变参数共存 | 练习 5 | 可变参数必须放在参数列表最后,否则编译报错 | | 可变参数综合应用 | 练习 5 | 求总分/平均分/最大值、字符串拼接 join |