说明:以下习题围绕今日授课核心内容——比较器排序(Comparable 自然排序 / Comparator 比较器排序) 展开,涵盖 Comparable 接口实现与
compareTo(T o)重写、Comparator 接口与匿名内部类、TreeSet/TreeMap 有序集合自动排序、返回值规则(负数存左 / 0 重复不存 / 正数存右)、Collections.sort(list, comparator)、Double.compare、compareTo 与 equals 一致性等知识点。题目分为 基础练习(Comparable 自然排序 / Comparator 比较器排序 / Collections.sort 排序,3 题)和 进阶挑战(比较器综合应用,2 题)。
以下练习围绕今日授课核心内容展开,由浅入深。
难度:⭐
知识点:Comparable<T> 接口、compareTo(T o) 重写、TreeSet 有序 + 去重、返回值规则
场景描述:TreeSet 是有序集合,取出的顺序是排序过的。不是所有类型都能直接存入 TreeSet——必须实现 Comparable 接口(自然排序),或在创建集合时于构造方法中传入 Comparator(比较器排序)。本节课 Student 类实现自然排序:按年龄升序,年龄相同再按姓名排序。
题目要求:
定义 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;
}
}
编写测试类 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
思考题:
难度:⭐⭐
知识点: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
思考题:
new TreeSet<>(new Comparator<Student>(){...}) 就能覆盖默认的自然排序?compare(o1, o2) 和 Comparable 的 compareTo(o) 写法有什么不同?(提示:一个是"两个对象在外部比较",一个是"this 与 o 比较")难度:⭐⭐
知识点: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
思考题:
(int)(s1.getScore() - s2.getScore())?用 Double.compare 有什么好处?(提示:浮点精度 + 强转截断)Collections.sort(list, comparator) 和 TreeSet 自动排序的区别是什么?(提示:一个改变已有 List 的顺序,一个在存入时排序;TreeSet 还会去重)以下练习综合运用 Comparable / Comparator、TreeMap、compareTo 与 equals 一致性等知识。
难度:⭐⭐⭐
知识点:TreeMap<K,V> 按键排序、自定义类型作为 Key、entrySet() 遍历、compareTo/equals 一致性
场景描述:TreeMap 是按键排序的有序 Map。把 Student 对象作为 Key、班级作为 Value 存入 TreeMap。因为 Student 实现了 Comparable,TreeMap 会自动按 compareTo 的规则对键排序——实现"成绩排行榜"。
题目要求:
定义 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;
}
}
编写测试类 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 - 二班
原理分析(必答):
compareTo 方法,返回 0 即视为重复new TreeMap<Student, String>() 会怎样?(提示:存入第一个键时抛 ClassCastException)难度:⭐⭐⭐⭐
知识点:Comparable 与 Comparator 结合、hashCode() / equals() 重写、compareTo 与 equals 一致性、多维度排序、迭代器遍历
场景描述:某公司开发员工信息管理系统。员工类 Employee(工号、姓名、薪资、部门)。要求:
题目要求:
定义 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() { ... }
}
编写测试类 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-财务部
原理分析(必答):
E002-李四 和 E003-王五 薪资相同(都是 15000)却能都存入 TreeSet?—— compareTo 返回 0 才算重复,薪资相同后还比较了工号,返回值不为 0public 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 岁两人按姓名排序)
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(成绩升序,与年龄无关)
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);
}
// 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(成绩降序)
// 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 没有索引,用迭代器统一遍历 |