KernelRanch
返回文章列表

Java TreeSet 为什么漏了一条任务?检查比较器何时返回 0

2026-11-13 #java#collections#backend

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 TreeSethttps://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/TreeSet.html2026-11-13比较等价关系和 Set 的约定
Java 17 Comparatorhttps://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Comparator.html2026-11-13与 equals 一致的定义以及 thenComparing

核验说明

  • Windows、OpenJDK 17.0.19 本机实测两个任务的 add、contains、remove 与组合排序。示例是单线程集合操作。