marp: true headingDivider: 6 theme: python-mini-pptx book: 大数据技术原理与应用 title: Java排序 subtitle: Java CompareTo author: 屈庆涛 date: 2026-09-13 14:00:00 categories: - Hadoop tags: - Hadoop - MapReduce - Partitioner mode: slide keywords: Mapper, Reducer, Partitioner, Hash, MapReduce,Job, Key semicolons: true
── mdcode2run:文档级执行默认 ──────────────────────¶
code-exec: java: JAVA_HOME: "D:/Program Files/Java/jdk-17.0.20.1" class-path: "D:\bigdata\hadoop\hadoop-3.1.3\share\hadoop\common*;D:\bigdata\hadoop\hadoop-3.1.3\share\hadoop\common\lib*" python: PYTHON_PATH: "D:/ProgramData/anaconda3/python.exe" mysql: HOST: db.example.com # PASSWORD 不能放这里(秘密只在引擎层) DATABASE: bigdata mr-job: name: WordCount types: in: LongWritable, Text map-out: Text, IntWritable out: Text, IntWritable combine: reduce input: data/bigdata.txt output: out/wordcount
一、学习目标¶
- 理解
Comparable接口与compareTo方法的作用 - 掌握
compareTo的返回值含义与排序约定 - 能正确实现自定义类的
compareTo方法 - 避免常见实现陷阱(溢出、一致性、null 处理)
- http://www.baidu.com
二、compareTo 是什么¶
compareTo 是 java.lang.Comparable<T> 接口中唯一的方法:
核心作用:定义对象的"自然顺序"(natural ordering),使对象可以被排序(Collections.sort、Arrays.sort、TreeSet、TreeMap、PriorityQueue 等都依赖它)。
返回值约定(三值语义):
| 返回值 | 含义 |
|---|---|
| 负数(通常 -1) | 当前对象 小于 参数对象 |
| 0 | 当前对象 等于 参数对象 |
| 正数(通常 1) | 当前对象 大于 参数对象 |
⚠️ 注意:只需关心符号,不要依赖具体数值大小。
return a - b;虽然常见,但有整数溢出风险(见第六节)。
三、compareTo 的契约(非常重要)¶
实现 compareTo 必须满足以下数学性质,否则 TreeSet、TreeMap 等有序集合会行为异常:
- 反对称性:
x.compareTo(y) == -y.compareTo(x) - 传递性:若
x.compareTo(y) > 0且y.compareTo(z) > 0,则x.compareTo(z) > 0 - 一致性:
x.compareTo(y) == 0应与x.equals(y) == true保持一致(强烈建议) - null 处理:
x.compareTo(null)应抛出NullPointerException(通常自动满足)
💡 为什么"一致性"重要?
TreeSet使用compareTo判断相等,HashSet使用equals。若两者不一致,同一个对象可能同时"存在"和"不存在"于不同集合,导致诡异 bug。
四、标准库示例¶
整型比较¶
Integer a = 5, b = 3;
System.out.println(a.compareTo(b)); // 1 (5 > 3)
System.out.println(b.compareTo(a)); // -1 (3 < 5)
System.out.println(a.compareTo(5)); // 0
字符串比较¶
排序使用¶
List<Integer> list = new ArrayList<>(List.of(5, 1, 3, 2, 4));
Collections.sort(list); // 依赖 Integer.compareTo
System.out.println(list); // [1, 2, 3, 4, 5]
五、自定义类实现 compareTo¶
示例 1:按单个字段排序¶
public class Student implements Comparable<Student> {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Student other) {
return Integer.compare(this.age, other.age); // ✅ 推荐写法
}
// getter、toString 略
}
使用:
List<Student> list = new ArrayList<>(List.of(
new Student("Alice", 22),
new Student("Bob", 20),
new Student("Carol", 21)
));
Collections.sort(list); // 按年龄升序
示例 2:多字段排序(先年龄,再姓名)¶
@Override
public int compareTo(Student other) {
int cmp = Integer.compare(this.age, other.age);
if (cmp != 0) return cmp;
return this.name.compareTo(other.name);
}
示例 3:降序排序¶
@Override
public int compareTo(Student other) {
return Integer.compare(other.age, this.age); // 交换参数顺序
}
六、常见陷阱¶
陷阱 1:整数减法溢出¶
// ❌ 错误:当 x = Integer.MIN_VALUE 且 y = 1 时溢出
public int compareTo(MyInt o) {
return this.value - o.value;
}
// ✅ 正确:使用包装类的 compare 静态方法
public int compareTo(MyInt o) {
return Integer.compare(this.value, o.value);
}
陷阱 2:与 equals 不一致¶
// ❌ 只按 id 比较,但 equals 按全部字段比较
public int compareTo(User o) { return this.id - o.id; }
// TreeSet 认为 id 相同即"相等",即使其他字段不同
// 若 equals 返回 false,语义矛盾
compareTo 与 equals 使用相同的比较字段,或至少保证"compareTo==0 ⟺ equals==true"。
陷阱 3:浮点数的特殊值¶
陷阱 4:null 参数¶
七、compareTo vs Comparator¶
| 特性 | Comparable / compareTo |
Comparator / compare |
|---|---|---|
| 位置 | 类内部实现 | 独立类或 lambda |
| 数量 | 一个类只能有一种自然顺序 | 可定义任意多种排序规则 |
| 修改原类 | 需要 | 不需要(可用在第三方类上) |
| 典型方法 | Collections.sort(list) |
list.sort(cmp) |
| 适用场景 | 类的默认/唯一顺序 | 临时/多规则排序 |
// Comparator 用法(无需修改 Student 类)
list.sort(Comparator.comparingInt(Student::getAge).reversed());
list.sort(Comparator.comparing(Student::getName));
建议:只有当类有明确且唯一的"自然顺序"(如
Integer、String、LocalDate)时才实现Comparable,否则优先用Comparator。
八、有序集合中的行为演示¶
TreeSet<Integer> set = new TreeSet<>();
set.add(3);
set.add(1);
set.add(2);
System.out.println(set); // [1, 2, 3] 自动按 compareTo 排序
TreeMap<Student, String> map = new TreeMap<>();
map.put(new Student("Alice", 22), "A");
map.put(new Student("Bob", 20), "B");
// 遍历时按 compareTo 定义的顺序输出
九、实践清单(Checklist)¶
实现 compareTo 时自检:
- 使用包装类
Integer.compare、Long.compare、Double.compare避免溢出 - 多字段比较是否逻辑清晰(短路返回)
- 是否与
equals保持一致性 - 是否处理了
null(抛 NPE 或显式校验) - 是否符合反对称性、传递性
- 类是否声明
implements Comparable<T>(泛型写具体类型,别写裸类型Comparable)
十、课后练习¶
- 基础:为
Book类(字段title、price、publishYear)实现compareTo,先按价格升序,价格相同按年份降序。 - 进阶:不修改
Book类,用Comparator实现相同规则,并比较两种方式在TreeSet中的效果。 - 陷阱题:解释为什么下面代码在
list = [MIN_VALUE, 0, 1]时会出错,并修复: - 设计题:一个
Person类实现compareTo只按id比较,equals按id + name比较,会导致什么 bug?给出两种修复方案。
十一、小结¶
compareTo定义对象的自然顺序,返回负 / 零 / 正表示小于 / 等于 / 大于- 必须满足反对称、传递、与
equals一致 - 实现时用
Integer.compare等工具方法避免溢出 - 多字段排序可链式编写
- 非唯一顺序或多规则场景,优选
Comparator
掌握 compareTo 是使用 Java 集合框架排序、以及理解 TreeSet/TreeMap 内部机制的基础,务必踏实验证其契约。