Java TreeSet 为什么漏了一条任务?检查比较器何时返回 0
Java 调度队列把不同任务放进 TreeSet 后只剩一条时,检查比较器是否只比较优先级。对 TreeSet 来说,比较结果为 0 就视作同一个元素,add()、contains() 和 remove() 都会受影响。
1. 重现入队、查找和移除
将代码保存为 TreeSetLab.java,用 JDK 17 运行 java TreeSetLab.java。
import java.util.Comparator;
import java.util.TreeSet;
class TreeSetLab {
record Job(int priority, String id) {}
static void require(boolean condition) {
if (!condition) {
throw new AssertionError("unexpected TreeSet result");
}
}
public static void main(String[] args) {
Job first = new Job(10, "job-a");
Job second = new Job(10, "job-b");
require(!first.equals(second));
Comparator<Job> byPriority = Comparator.comparingInt(Job::priority);
var broken = new TreeSet<>(byPriority);
require(broken.add(first));
boolean added = broken.add(second);
boolean found = broken.contains(second);
boolean removed = broken.remove(second);
require(!added && found && removed && broken.isEmpty());
System.out.printf(
"priority only: add=%b contains=%b remove=%b remaining=%d%n",
added, found, removed, broken.size()
);
var ordered = new TreeSet<>(byPriority.thenComparing(Job::id));
require(ordered.add(second) && ordered.add(first));
require(!ordered.add(new Job(10, "job-a")));
var ids = ordered.stream().map(Job::id).toList();
require(ids.equals(java.util.List.of("job-a", "job-b")));
System.out.println("priority then id: " + ids);
}
}
priority only: add=false contains=true remove=true remaining=0
priority then id: [job-a, job-b]
2. 先确定什么叫同一条任务
样本的 Job 是不可变 record,equals() 比较优先级和 ID。补上 ID 后,比较器也用这两个字段确定等价关系。此时任务优先级变化意味着新的排序键。若业务把 ID 当唯一身份,更新优先级时应先找到旧条目,再更新有序索引。可以由 Map<Id, Job> 维护身份,通过旧对象移除 TreeSet 条目后插入新对象。
已有条目若持有可变字段,原地修改参与比较的值会破坏集合的顺序假设。排查时可为同优先级不同 ID、同 ID 更新优先级和删除不存在的任务分别写用例。前两种用例覆盖了排序键与业务身份的边界,第三种能发现删除误命中同优先级任务。
小结
TreeSet 用比较器同时决定顺序和集合等价关系。比较器返回 0 的条件应和集合中元素的等价定义一致。
数据来源与检索时间
| 来源 | URL | 检索日期 | 对应内容 |
|---|---|---|---|
| Java 17 TreeSet | https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/TreeSet.html | 2026-11-13 | 比较等价关系和 Set 的约定 |
| Java 17 Comparator | https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Comparator.html | 2026-11-13 | 与 equals 一致的定义以及 thenComparing |
核验说明
- Windows、OpenJDK 17.0.19 本机实测两个任务的 add、contains、remove 与组合排序。示例是单线程集合操作。