Wrayの知识库 Wrayの知识库
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • 计算机基础
  • 操作系统原理
  • Linux
  • MacOS
  • Windows
  • 系统工程与研究专题
  • AI 基础
  • 大模型基础
  • Prompt 工程
  • RAG 检索增强生成
  • Agent 智能体
  • AI 应用开发
  • AI 工程化
  • AI 安全与治理
  • AI 面试与设计题
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • 计算机基础
  • 操作系统原理
  • Linux
  • MacOS
  • Windows
  • 系统工程与研究专题
  • AI 基础
  • 大模型基础
  • Prompt 工程
  • RAG 检索增强生成
  • Agent 智能体
  • AI 应用开发
  • AI 工程化
  • AI 安全与治理
  • AI 面试与设计题
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
  • Java章节编写规范
  • Java基础

  • Java集合

    • Java集合概述
    • ArrayList
    • LinkedList
    • HashMap
    • LinkedHashMap
    • HashSet
    • TreeMap
    • Queue&Deque
    • 迭代器与遍历机制
    • Collections工具类
    • 集合排序与比较器
      • 1. Comparable
      • 2. Comparator
      • 3. compare 返回值
      • 4. 比较器约定
      • 5. List 排序
      • 6. Stream 排序
      • 7. TreeMap/TreeSet 排序
      • 8. null 排序
      • 9. 稳定排序
      • 10. 排序性能
      • 11. 常见业务排序
      • 12. 排序与数据库
      • Tips 快问快答
    • 集合选型与常见问题
  • Java并发

  • Java IO

  • JVM

目录

集合排序与比较器

排序是集合处理中非常常见的需求。Java 中排序主要依赖两套机制:

  • Comparable:对象自己定义自然顺序。
  • Comparator:外部定义比较规则。

理解排序机制不仅能写出正确的排序代码,也能避免 TreeMap、TreeSet 中元素“莫名丢失”的问题。

# 1. Comparable

Comparable 表示对象自身可以比较。

public class User implements Comparable<User> {
    private Long id;
    private int age;

    @Override
    public int compareTo(User other) {
        return Integer.compare(this.age, other.age);
    }
}

排序:

List<User> users = new ArrayList<>();
Collections.sort(users);

自然顺序适合稳定、唯一、大家都认可的默认排序,例如:

  • 数字按大小。
  • 字符串按字典序。
  • 日期按时间先后。

业务对象通常不一定有唯一自然顺序,因此更常用 Comparator。

# 2. Comparator

Comparator 是外部比较器。

users.sort(Comparator.comparing(User::getAge));

多字段排序:

users.sort(
        Comparator.comparing(User::getAge)
                .thenComparing(User::getId)
);

倒序:

users.sort(Comparator.comparing(User::getAge).reversed());

空值处理:

users.sort(
        Comparator.comparing(
                User::getName,
                Comparator.nullsLast(String::compareTo)
        )
);

# 3. compare 返回值

比较方法返回整数:

负数:this &lt; other
零:this == other
正数:this > other

不要依赖具体返回值大小,只关心正负和零。

不推荐:

(a, b) -> a.getAge() - b.getAge()

风险:整数溢出。

推荐:

(a, b) -> Integer.compare(a.getAge(), b.getAge())

或者:

Comparator.comparingInt(User::getAge)

# 4. 比较器约定

比较器必须满足:

约定 说明
自反性 compare(a, a) == 0
反对称 compare(a, b) 与 compare(b, a) 符号相反
传递性 a > b 且 b > c,则 a > c
一致性 多次比较同一对象结果稳定

错误比较器可能导致排序异常、TreeMap 覆盖 key、TreeSet 丢元素。

# 5. List 排序

List<Integer> numbers = new ArrayList<>(List.of(3, 1, 2));
numbers.sort(Integer::compareTo);

或:

Collections.sort(numbers);

排序会直接修改原列表:

原列表 [3, 1, 2]
sort
原列表 [1, 2, 3]

如果不想修改原列表:

List<Integer> sorted = new ArrayList<>(numbers);
sorted.sort(Integer::compareTo);

# 6. Stream 排序

List<User> sorted = users.stream()
        .sorted(Comparator.comparing(User::getAge))
        .toList();

stream().sorted() 返回新结果,不修改原集合。

对比:

方式 是否修改原集合 适合场景
list.sort 是 原地排序
Collections.sort 是 原地排序
stream.sorted 否 生成排序后的新结果

# 7. TreeMap/TreeSet 排序

TreeMap 和 TreeSet 按比较器组织数据。

Set<User> set = new TreeSet<>(
        Comparator.comparing(User::getAge)
);

如果两个用户 age 相同,比较结果为 0,TreeSet 会认为它们重复。

User{id=1, age=18}
User{id=2, age=18}

Comparator 只比较 age
compare 返回 0
TreeSet 只保留一个

修复:

Set<User> set = new TreeSet<>(
        Comparator.comparing(User::getAge)
                .thenComparing(User::getId)
);

比较器用于排序集合时,必须能区分业务上不应重复的对象。

# 8. null 排序

字段可能为 null 时:

users.sort(
        Comparator.comparing(
                User::getName,
                Comparator.nullsLast(String::compareTo)
        )
);

nullsFirst:

null, A, B, C

nullsLast:

A, B, C, null

不要在比较器里直接调用可能为 null 的字段方法。

# 9. 稳定排序

稳定排序指两个元素比较结果相等时,排序后仍保持原相对顺序。

原始:
A(age=18), B(age=20), C(age=18)

按 age 稳定排序:
A(age=18), C(age=18), B(age=20)

Java 对对象数组和 List 的排序通常是稳定排序。稳定性对多轮排序、报表排序很重要。

# 10. 排序性能

排序复杂度通常是 O(n log n)。比较器越复杂,排序成本越高。

优化建议:

  • 避免比较器中调用远程接口或数据库。
  • 避免比较器中做复杂计算。
  • 可以先预计算排序字段。
  • 大数据量排序尽量交给数据库或搜索引擎。

错误示例:

users.sort((a, b) -> {
    BigDecimal scoreA = remoteService.queryScore(a.getId());
    BigDecimal scoreB = remoteService.queryScore(b.getId());
    return scoreA.compareTo(scoreB);
});

比较器会被调用很多次,这种写法会非常慢。

# 11. 常见业务排序

按创建时间倒序:

orders.sort(
        Comparator.comparing(Order::getCreateTime).reversed()
);

按状态优先级:

Map<OrderStatus, Integer> priority = Map.of(
        OrderStatus.PAID, 1,
        OrderStatus.CREATED, 2,
        OrderStatus.CANCELED, 3
);

orders.sort(Comparator.comparing(order -> priority.get(order.getStatus())));

按多个字段:

orders.sort(
        Comparator.comparing(Order::getStatus)
                .thenComparing(Order::getCreateTime, Comparator.reverseOrder())
                .thenComparing(Order::getId)
);

# 12. 排序与数据库

如果数据来自数据库,优先考虑在 SQL 中排序:

ORDER BY create_time DESC, id ASC

适合 Java 内存排序的情况:

  • 数据量不大。
  • 排序字段由 Java 计算得出。
  • 数据来自多个来源,需要合并后排序。
  • 排序只是展示前的小范围处理。

大数据量分页排序不要拉到内存里做。

# Tips 快问快答

Q:Comparable 和 Comparator 有什么区别? A:Comparable 是对象自身的自然顺序,Comparator 是外部传入的比较规则。

Q:比较器返回 0 表示什么? A:表示两个元素在当前比较规则下相等。

Q:为什么 TreeSet 会丢元素? A:比较器返回 0 的元素会被认为重复,即使 equals 不相等。

Q:比较整数为什么不用相减? A:可能溢出,应用 Integer.compare 或 Comparator.comparingInt。

Q:list.sort 会修改原列表吗? A:会。

Q:stream.sorted 会修改原集合吗? A:不会,它生成新的排序结果。

Q:字段可能为 null 怎么排序? A:使用 Comparator.nullsFirst 或 nullsLast。

Q:排序一定要在 Java 内存里做吗? A:不是。数据库数据量大时优先用 SQL 排序。

Q:比较器里能调用远程接口吗? A:不建议,比较器会被调用很多次,性能和稳定性都差。

Q:多字段排序怎么写? A:使用 Comparator.comparing(...).thenComparing(...)。

上次更新: 2026/06/24, 16:37:44
Collections工具类
集合选型与常见问题

← Collections工具类 集合选型与常见问题→

Copyright © 2023-2026 Wray | 鄂ICP备2024050235号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式