20260803-比较器排序练习.md 28 KB

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<T> 接口、compareTo(T o) 重写、TreeSet 有序 + 去重、返回值规则

场景描述:TreeSet 是有序集合,取出的顺序是排序过的。不是所有类型都能直接存入 TreeSet——必须实现 Comparable 接口(自然排序),或在创建集合时于构造方法中传入 Comparator(比较器排序)。本节课 Student 类实现自然排序:按年龄升序,年龄相同再按姓名排序。

题目要求:

  1. 定义 Student 类(name、age、score 三个属性),实现 Comparable<Student> 接口,重写 compareTo(T o) 方法:

    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 = this.age - o.getAge();
        //       return result == 0 ? this.name.compareTo(o.getName()) : result;
    }
    }
    
  2. 编写测试类 NaturalSortTest:

    import java.util.TreeSet;
    
    public class NaturalSortTest {
    public static void main(String[] args) {
        // 1. 创建 TreeSet<Student>(不传 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<T> 接口、compare(o1, o2) 重写、匿名内部类、TreeSet(Comparator) 构造方法

场景描述:类设计期间已经实现 Comparable(默认按年龄排序),但现在临时需要按别的规则排序(如成绩),又不修改 Student 类。此时使用比较器排序——创建集合对象时,在构造方法中传入 Comparator 接口的实现类对象(匿名内部类)。

题目要求:

import java.util.Comparator;
import java.util.TreeSet;

public class ComparatorSortTest {
    public static void main(String[] args) {
        // 1. 创建 TreeSet<Student>,构造方法传入匿名内部类 Comparator<Student>
        //    排序规则:按成绩(score)升序,成绩相同再按姓名排序
        //    提示:TreeSet<Student> set = new TreeSet<>(new Comparator<Student>() {
        //              @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<Student>(){...}) 就能覆盖默认的自然排序?
  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 原地排序(改变原集合顺序)。

题目要求:

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class ListSortTest {
    public static void main(String[] args) {
        // 1. 创建 ArrayList<Student>,添加 4 名学生:张三20-85 / 李四22-90 / 王五19-78 / 赵六21-88
        // 2. 匿名内部类 Comparator:按年龄升序排序(s1.getAge() - s2.getAge())
        //    Collections.sort(list, new Comparator<Student>() {...});
        //    遍历输出

        // 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 累加求和:

    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):

    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<K,V> 按键排序、自定义类型作为 Key、entrySet() 遍历、compareTo/equals 一致性

场景描述:TreeMap 是按键排序的有序 Map。把 Student 对象作为 Key、班级作为 Value 存入 TreeMap。因为 Student 实现了 Comparable,TreeMap 会自动按 compareTo 的规则对键排序——实现"成绩排行榜"。

题目要求:

  1. 定义 Student 类(实现 Comparable,按成绩降序排序,成绩相同按姓名),修改 compareTo:

    public class Student implements Comparable<Student> {
    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:

    import java.util.Map;
    import java.util.TreeMap;
    
    public class ScoreRankingTest {
    public static void main(String[] args) {
        // 1. 创建 TreeMap<Student, String>:学生 → 所在班级
        //    提示:Map<Student, String> 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<Student, String>() 会怎样?(提示:存入第一个键时抛 ClassCastException)

进阶 2:员工信息管理系统(多排序规则 + compareTo/equals 一致性 + 综合应用)

难度:⭐⭐⭐⭐ 知识点:Comparable 与 Comparator 结合、hashCode() / equals() 重写、compareTo 与 equals 一致性、多维度排序、迭代器遍历

场景描述:某公司开发员工信息管理系统。员工类 Employee(工号、姓名、薪资、部门)。要求:

  • 默认排序(自然排序):按薪资降序,薪资相同再按工号
  • 员工加入 TreeSet 后自动排序且自动去重(工号相同的员工视为同一人)
  • 临时需求:按姓名排序输出一份通讯录(使用 Comparator 比较器排序,不修改 Employee 类)

题目要求:

  1. 定义 Employee 类:

    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) {
        // 提示: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:

    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-市场部
    
        // 2. 迭代器遍历输出 → 观察:按薪资降序,薪资相同按工号;工号重复的自动去重
        //    输出格式:"E001-张三-12000.0-技术部"
    
        // 3. 临时需求:通讯录按姓名排序 —— 创建 TreeSet<Employee> 传入按姓名排序的 Comparator
        //    匿名内部类:o1.getName().compareTo(o2.getName())
        //    迭代器遍历输出
    
        // 4. 思考:E001-张三(12000) 和 E005-刘七(12000) 薪资相同,为何都能存入?
        //    —— compareTo 在薪资相同后继续比较工号,返回值不为 0
    }
    }
    

预期输出:

========== 默认排序:按薪资降序(薪资相同按工号) ==========
E002-李四-15000.0-市场部
E003-王五-15000.0-技术部    ← 与李四薪资相同 → 按工号 E002<E003
E001-张三-12000.0-技术部
E005-刘七-12000.0-市场部    ← 与张三薪资相同 → 按工号 E001<E005
E004-赵六-9000.0-财务部
========== 通讯录:按姓名排序 ==========
E002-李四-15000.0-市场部
E005-刘七-12000.0-市场部
E003-王五-15000.0-技术部
E001-张三-12000.0-技术部
E004-赵六-9000.0-财务部

原理分析(必答):

  1. 为什么 E002-李四 和 E003-王五 薪资相同(都是 15000)却能都存入 TreeSet?—— compareTo 返回 0 才算重复,薪资相同后还比较了工号,返回值不为 0
  2. 官方推荐"compareTo == 0 → equals 返回 true、hashCode 一致",Employee 中应该按什么来重写 equals/hashCode?—— 按与 compareTo 一致的判定依据(本例为工号)
  3. 为什么两次遍历用迭代器而不是 for + get(i)?—— Set 没有索引,Iterator 是单列集合的通用遍历方式

参考答案要点

练习 1 参考

public class Student implements Comparable<Student> {
    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<Student> 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 参考

TreeSet<Student> set = new TreeSet<>(new Comparator<Student>() {
    @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 参考

ArrayList<Student> 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<Student>() {
    @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<Student>() {
    @Override
    public int compare(Student s1, Student s2) {
        return Double.compare(s2.getScore(), s1.getScore());
    }
});
for (Student s : list) {
    System.out.println(s);
}

练习 4 参考

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 参考

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 参考

// 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<Student, String> 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<Student, String> me : map.entrySet()) {
    System.out.println(me.getKey().getName() + " " + me.getKey().getScore() + " - " + me.getValue());
}
// 输出顺序:王五95 → 张三90 → 赵六88 → 李四85(成绩降序)

进阶 2 参考(核心逻辑)

// 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<Employee> 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<Employee> it = set.iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}
// 输出:E002 → E003 → E001 → E005 → E004(薪资降序,同薪按工号)

// 通讯录:按姓名排序的临时 Comparator
TreeSet<Employee> byName = new TreeSet<>(new Comparator<Employee>() {
    @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