Tree 集合中的自定义排序
向 TreeMap 或 TreeSet 提供 Comparator,以定义特定领域的排序规则
Tree 集合中的自定义排序 是 CoddyKit 上的免费 Java Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Java Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Java Academy 课程共包含 4 节课。
为什么需要自定义顺序?
树形集合(TreeMap、TreeSet)默认使用自然顺序。当您的领域对象没有自然顺序,或者您需要使用不同的排序方式时,请在构造时提供 Comparator。
构造 TreeMap 时使用 Comparator
传入 Comparator,按照自定义规则对键排序——例如按字母逆序排列:
import java.util.*;
TreeMap<String, Integer> map = new TreeMap<>(Comparator.reverseOrder());
map.put("banana", 2);
map.put("apple", 1);
map.put("cherry", 3);
map.forEach((k,v) -> System.out.println(k)); // cherry, banana, apple构造 TreeSet 时使用 Comparator
向 TreeSet 提供 Comparator,即可独立于元素的自然顺序控制排序顺序:
TreeSet<String> byLength = new TreeSet<>(
Comparator.comparingInt(String::length)
.thenComparing(Comparator.naturalOrder())
);
byLength.addAll(List.of("fig","apple","kiwi","date","banana"));
for (String s : byLength) System.out.print(s + " ");
// fig date kiwi apple banana按多个字段对对象排序
链接多个 Comparator,先按主要字段排序,再按次要字段作为平局时的比较依据:
record Employee(String name, String dept, int salary) {}
TreeSet<Employee> sorted = new TreeSet<>(
Comparator.comparing(Employee::dept)
.thenComparingInt(Employee::salary).reversed()
.thenComparing(Employee::name)
);
sorted.add(new Employee("Alice", "Eng", 90_000));
sorted.add(new Employee("Bob", "Eng", 85_000));
sorted.add(new Employee("Carol", "HR", 70_000));
for (Employee e : sorted) System.out.println(e.dept()+" "+e.name());与相等判断的一致性
comparator.compare(a, b) == 0,则 a 和 b 会被视为同一个键——即使 a.equals(b) 为 false 也是如此。// Comparator ignoring case — "apple" and "APPLE" become the same key!
TreeSet<String> ci = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
ci.add("apple");
ci.add("APPLE"); // not added — same by comparator
System.out.println(ci.size()); // 1不区分大小写的 TreeMap
一个常见的实际需求是:创建一个键为不区分大小写字符串的 TreeMap(适用于 HTTP 标头和配置键):
TreeMap<String, String> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
headers.put("Content-Type", "application/json");
headers.put("content-type", "text/html"); // overwrites!
System.out.println(headers.get("CONTENT-TYPE")); // text/html
System.out.println(headers.size()); // 1空值安全的 Comparator
使用自然顺序的树形集合遇到空值键时会抛出 NPE。请使用空值安全的 Comparator,使空值可以作为最小键:
TreeMap<String, Integer> map = new TreeMap<>(
Comparator.nullsFirst(Comparator.naturalOrder())
);
map.put(null, 0);
map.put("b", 2);
map.put("a", 1);
map.forEach((k,v) -> System.out.println(k + "=" + v));
// null=0, a=1, b=2领域对象示例:按价格排序的 Product
在 TreeSet 中按价格对商品排序,并以名称作为平局时的比较依据,从而保持唯一性:
record Product(String name, double price) {}
TreeSet<Product> catalog = new TreeSet<>(
Comparator.comparingDouble(Product::price)
.thenComparing(Product::name)
);
catalog.add(new Product("Widget", 9.99));
catalog.add(new Product("Gadget", 24.99));
catalog.add(new Product("Donut", 9.99));
catalog.forEach(p -> System.out.println(p.name() + " $" + p.price()));
// Donut $9.99, Widget $9.99, Gadget $24.99获取 Comparator
在 TreeMap/TreeSet 上调用 comparator(),即可获取自定义 Comparator。如果使用的是自然顺序,则返回 null。
TreeMap<String, Integer> map = new TreeMap<>(Comparator.reverseOrder());
System.out.println(map.comparator() != null); // true
TreeMap<String, Integer> natural = new TreeMap<>();
System.out.println(natural.comparator()); // nullComparator 组合
Java 的 Comparator 提供了可链式调用的工厂方法,便于进行简洁的组合:
Comparator<String> comp =
Comparator.comparingInt(String::length) // by length
.thenComparing(Comparator.naturalOrder()); // then alphabetically
TreeSet<String> ts = new TreeSet<>(comp);
ts.addAll(List.of("go", "java", "c", "rust", "py"));
ts.forEach(s -> System.out.print(s + " "));
// c go py java rust何时不应使用自定义 Comparator(NOT)
避免在 TreeMap 的键或 TreeSet 的元素上使用与相等性不一致的 Comparator——这会导致令人困惑的“缺失”Entry。为确保在所有集合场景中行为正确,请保证 compare(a, b) == 0 当且仅当 a.equals(b)。
快速检查
一个 TreeSet 仅使用基于 String::length 的 Comparator。添加 "cat" 和 "dog" 后会发生什么?
回顾:自定义排序
要点回顾:
- 将 Comparator 传入 TreeMap/TreeSet 构造方法,以定义自定义排序
- 由 Comparator 定义的相等性决定树集合中的唯一性
- 确保比较器与 equals 保持一致,避免出现意外结果
- 使用 Comparator.nullsFirst/nullsLast 实现对 null 安全的排序
- 使用 thenComparing 链式组合,实现多字段排序
常见问题解答
「Tree 集合中的自定义排序」课时是免费的吗?
是的 — 「Tree 集合中的自定义排序」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Java Academy 课程的其余内容,请升级到 CoddyKit PRO。 Java Academy 课程共包含 4 节课。
「Tree 集合中的自定义排序」这节课中我会学到什么?
向 TreeMap 或 TreeSet 提供 Comparator,以定义特定领域的排序规则 你通过在浏览器中直接运行的动手代码来练习 Java Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Java Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Java Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「Tree 集合中的自定义排序」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Java Academy 课中编写并运行代码吗?
能。每节 Java Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- TreeMap:有序键值对
- 子映射与范围视图
- TreeSet 与 NavigableSet
- Tree 集合中的自定义排序