1. 本文将教会你什么(排序 Java List 的最简方法)
在使用 Java 时,对 List 进行排序 是极其常见的需求。
与此同时,许多开发者——尤其是初学者——常常被以下问题困扰:
- 我应该使用哪种方法来排序 List?
list.sort()与Collections.sort()有何区别?- 如何对对象列表而不是简单值进行排序?
本文旨在 清晰且实用 地回答这些问题,先给出结论,再逐步解释细节和实际使用场景。
1.1 结论:只需记住这一种模式
如果你只想要 最简且最标准的 Java List 排序方式,就是下面这样:
list.sort(Comparator.naturalOrder());
这会以 升序(从小到大、A 到 Z、从旧到新)对 List 进行排序。
如果想要 降序,使用下面的代码:
list.sort(Comparator.reverseOrder());
仅凭这两行代码,你已经可以对以下类型的 List 进行排序:
IntegerStringLocalDate- 以及大多数其他常见类型
对于日常的大多数场景,这已经足够。
1.2 对对象列表进行排序:指定排序键
在真实项目中,List 往往存放的是 对象,而不是简单值。
例如:
class Person {
private String name;
private int age;
// getters omitted
}
要按年龄对 List<Person> 进行排序,只需写:
list.sort(Comparator.comparing(Person::getAge));
这行代码的含义是:
- 从每个
Person中提取age - 对这些值进行比较
- 以升序方式对 List 排序
你无需手动实现比较逻辑。只要记住下面的模式:
Comparator.comparing(要比较的属性)
1.3 本文覆盖的内容
本文从基础到实战,系统讲解 Java List 排序,包括:
list.sort()与Collections.sort()的区别Comparator的简明工作原理- 多条件排序(例如:年龄 → 姓名)
- 同时使用升序和降序
- 安全处理
null值的方法 - 何时使用
stream().sorted()替代list.sort()
目标不是让你死记硬背,而是 理解每种方式背后的原因。
1.4 适合阅读本文的对象
本文面向的读者是:
- 已掌握基本的 Java 语法(类、方法、List)
- 使用过
ArrayList或List的开发者 - 每次需要排序时仍然要去查找代码示例的程序员
本文 不 针对从未写过 Java 代码的绝对初学者,但对想快速掌握实用 Java 编程的开发者仍然友好。
2. Java 中对 List 排序的三种常见方式(该选哪一种?)
在 Java 中,对 List 排序并非只有唯一的做法。实际工作中,开发者主要使用 三种不同的方案,它们在行为和意图上略有差别。
在深入 Comparator 之前,先了解 每种方案出现的时机和原因。
2.1 list.sort(Comparator) — 现代且推荐的做法
自 Java 8 起,这已经成为 标准且最常推荐的 List 排序方式。
list.sort(Comparator.naturalOrder());
关键特性
- 直接定义在
List接口上 - 代码简洁、可读性高
- 在原地修改 List(破坏性)
对对象的排序方式同样适用:
list.sort(Comparator.comparing(Person::getAge));
何时使用
- 你可以接受修改原始 List
- 你希望得到最清晰、最简洁的实现
👉 如果不确定该选哪种,list.sort() 往往是最佳选择。
2.2 Collections.sort(list) — 仍需了解的旧式写法
在较老的教程或遗留项目中,你可能会看到如下代码:
Collections.sort(list);
或者配合 Comparator 使用:
Collections.sort(list, Comparator.reverseOrder());
关键特性
- 自 Java 1.2 起存在
- 内部行为几乎与
list.sort相同 - 也会修改原始 List
为什么它在今天不太常用
- Java 8 引入了
list.sort,使用起来更自然 - 通过
Collections对 List 进行排序不够直观
对于新代码,推荐使用 list.sort。然而,在阅读旧代码库时,了解 Collections.sort 仍然很重要。
2.3 stream().sorted() — 非破坏性排序
第三种方式使用 Stream API。
List<Integer> sorted =
list.stream()
.sorted()
.toList();
使用 Comparator:
List<Person> sorted =
list.stream()
.sorted(Comparator.comparing(Person::getAge))
.toList();
关键特性
- 不会修改原始 List
- 返回一个新的已排序 List
- 易于与
filter、map以及其他流操作组合使用
何时使用
- 当必须保持原始 List 不变时
- 当排序是数据处理管道的一部分时
然而,对于简单的排序,list.sort 通常更清晰且更高效。
2.4 如何选择(快速决策指南)
| Goal | Recommended Method |
|---|---|
| Sort a List directly | list.sort() |
| Understand or maintain old code | Collections.sort() |
| Keep the original List unchanged | stream().sorted() |
此时,请记住唯一的规则:
除非有明确的理由,否则请使用
list.sort()。
3. 基础排序:升序和降序
现在你已经了解 使用哪种排序方法,让我们关注最常见的需求:升序或降序排序。
本节涵盖数字、字符串和日期等基本类型的 List——后续所有内容的基础。
3.1 升序排序(自然顺序)
许多 Java 类型都有 自然顺序,例如:
- 数字 → 从小到大
- 字符串 → 按字母顺序(A 到 Z)
- 日期 → 从早到晚
要对 List 进行升序排序,使用:
list.sort(Comparator.naturalOrder());
示例:数字排序
List<Integer> numbers = Arrays.asList(5, 1, 3, 2);
numbers.sort(Comparator.naturalOrder());
结果:
[1, 2, 3, 5]
示例:字符串排序
List<String> names = Arrays.asList("Tom", "Alice", "Bob");
names.sort(Comparator.naturalOrder());
结果:
[Alice, Bob, Tom]
👉 如果只需要升序,这是最简单且最安全的方法。
3.2 降序排序
要反转自然顺序,使用 Comparator.reverseOrder()。
list.sort(Comparator.reverseOrder());
示例:数字降序
List<Integer> numbers = Arrays.asList(5, 1, 3, 2);
numbers.sort(Comparator.reverseOrder());
结果:
[5, 3, 2, 1]
示例:字符串降序
List<String> names = Arrays.asList("Tom", "Alice", "Bob");
names.sort(Comparator.reverseOrder());
结果:
[Tom, Bob, Alice]
3.3 当可以省略 Comparator 时
在某些情况下,你可能会看到没有显式 Comparator 的排序代码。
Collections.sort(list);
甚至是:
list.sort(null);
这些仅在以下情况下有效 仅当:
- 元素实现了
Comparable接口 - 你想要自然(升序)顺序
虽然有效,但这些写法不够明确。
在实际代码库中,通常更倾向于使用以下更清晰的写法:
list.sort(Comparator.naturalOrder());
3.4 常见误解:谁决定排序顺序?
初学者常常误以为:
sort()决定升序或降序
实际上:
sort()执行排序Comparator定义了元素的比较方式
一旦理解了这种职责分离,其他所有内容——对象排序、多条件以及 null 处理——都将变得更容易。
4. 对象列表排序:理解 Comparator
在实际的 Java 应用中,你很少会对 Integer 或 String 这类简单值进行排序。大多数情况下,你会对 自定义对象的 List 进行排序。
这就是 Comparator 成为核心概念的地方。
4.1 Comparator 是什么?
Comparator 定义了 两个元素应如何比较。
从概念上讲,它回答了以下问题:
“给定两个对象,哪个应该排在前面?”
在内部,Comparator 返回:
- 一个 负数 → 第一个元素排在前面
- 零 → 顺序无关紧要
- 一个 正数 → 第二个元素排在前面
幸运的是,在现代 Java 中,你几乎不需要手动实现这段逻辑。
4.2 使用 Comparator.comparing 按字段排序
考虑以下类:
class Person {
private String name;
private int age;
// getters omitted
}
要按年龄对 List<Person> 进行排序,使用:
list.sort(Comparator.comparing(Person::getAge));
这段代码的含义很自然:
- 取每个
Person - 提取
age - 比较这些值
- 按升序对 List 进行排序
这一行代码取代了以前需要多行的比较代码。
4.3 按字符串、日期及其他类型排序
相同的模式几乎适用于任何字段类型。
按名称排序
list.sort(Comparator.comparing(Person::getName));
按日期排序
list.sort(Comparator.comparing(Person::getBirthDate));
只要提取的值具有自然顺序,Comparator.comparing 就能在无需额外设置的情况下工作。
4.4 使用针对原始类型的 Comparator
对于数值字段,Java 提供了优化的方法:
comparingIntcomparingLongcomparingDouble
示例:
list.sort(Comparator.comparingInt(Person::getAge));
这些方法:
- 避免不必要的对象装箱
- 使意图更清晰
- 对大 List 来说稍微更高效
虽然差异不大,但它们被视为数值字段的最佳实践。
4.5 为什么这很重要
一旦你理解了 Comparator.comparing,就可以实现:
- 多条件排序
- 升序和降序混合排序
- 安全处理
null值
换句话说,这就是 Java 实际 List 排序的基础。
5. 多条件排序(最常见的实际模式)
在实际应用中,仅按单个字段排序往往不够。通常需要 二级和三级条件 来创建稳定且有意义的顺序。
例如:
- 先按年龄排序,再按名称排序
- 先按优先级排序,再按时间戳排序
- 先按分数(降序)排序,再按 ID(升序)排序
Java 的 Comparator API 正是为此而设计的。
5.1 thenComparing 的基础
多条件排序遵循一个简单规则:
如果两个元素在第一个条件上相等,则使用下一个条件。
下面是基本模式:
list.sort(
Comparator.comparingInt(Person::getAge)
.thenComparing(Person::getName)
);
这意味着:
- 按
age(升序)排序 - 如果年龄相等,则按
name(升序)排序
这会产生一致且可预测的顺序。
5.2 混合升序和降序
经常会出现一个字段需要降序,另一个字段需要升序的情况。
示例:分数(降序),名称(升序)
list.sort(
Comparator.comparingInt(Person::getScore).reversed()
.thenComparing(Person::getName)
);
重要细节:
reversed()只作用于它前面的 Comparator
这使得组合不同的排序方向变得安全。
5.3 将 Comparator 传递给 thenComparing
为了更好的可读性,你可以在 thenComparing 中显式定义排序方向。
示例:年龄(升序),注册日期(降序)
list.sort(
Comparator.comparingInt(Person::getAge)
.thenComparing(
Comparator.comparing(Person::getRegisterDate).reversed()
)
);
这种风格非常清晰地显示哪些字段是升序或降序,
这对代码审查和长期维护很有帮助。
5.4 一个真实的业务示例
list.sort(
Comparator.comparingInt(Order::getPriority)
.thenComparing(Order::getDeadline)
.thenComparing(Order::getOrderId)
);
排序逻辑:
- 优先级更高的排在前面
- 截止日期更早的排在前面
- 订单 ID 更小的排在前面
这确保了排序的稳定性并符合业务需求。

5.5 保持多条件排序的可读性
随着排序逻辑的增长,可读性比简洁性更重要。
最佳实践:
- 为每个条件换行
- 避免深度嵌套的 lambda 表达式
- 如果业务规则不明显,添加注释
清晰的排序逻辑为以后阅读代码的所有人节省时间。
6. 安全处理 null 值(非常常见的错误来源)
在对真实数据进行排序时,null 值几乎不可避免。
字段可能是可选的,旧数据可能不完整,或者值可能根本缺失。
如果不显式处理 null,排序很容易在运行时失败。
6.1 为什么 null 在排序时会导致问题
考虑以下代码:
list.sort(Comparator.comparing(Person::getName));
如果 getName() 对任何元素返回 null,Java 在比较时会抛出
NullPointerException。
出现这种情况的原因是:
Comparator假设值可以比较- 除非你定义,否则
null没有自然顺序
因此,必须显式处理 null。
6.2 使用 nullsFirst 和 nullsLast
Java 提供了帮助方法来定义 null 值的排序方式。
将 null 值放在前面
list.sort(
Comparator.comparing(
Person::getName,
Comparator.nullsFirst(Comparator.naturalOrder())
)
);
将 null 值放在后面
list.sort(
Comparator.comparing(
Person::getName,
Comparator.nullsLast(Comparator.naturalOrder())
)
);
这些做法:
- 防止
NullPointerException - 使排序规则明确且易读
6.3 当列表本身包含 null 元素时
有时,列表的元素本身 可能是 null。
List<Person> list = Arrays.asList(
new Person("Alice", 20),
null,
new Person("Bob", 25)
);
安全处理方法如下:
list.sort(
Comparator.nullsLast(
Comparator.comparing(Person::getName)
)
);
这确保了:
null元素被移动到末尾- 非
null元素正常排序
6.4 小心使用 comparingInt 与 null
像 comparingInt 这样的原始类型比较器 无法处理 null。
Comparator.comparingInt(Person::getAge); // age must be int
如果该字段是可能为 null 的 Integer,请使用:
Comparator.comparing(
Person::getAge,
Comparator.nullsLast(Integer::compare)
);
这可以避免意外的运行时错误。
6.5 将 null 处理视为规范的一部分
决定 null 值是否出现的方式:
- 在开头
- 在末尾
- 或者完全过滤掉
是一个 业务决策,而不仅仅是技术决定。
通过使用 nullsFirst 或 nullsLast,你可以直接在代码中记录该决策——
使排序逻辑更安全、更易于理解。
7. 常见陷阱和错误(如何避免细微的 Bug)
在 Java 中对 List 进行排序看似简单,但存在若干 容易忽视的陷阱,可能导致 Bug、意外行为或性能问题。
提前了解这些内容可以在调试和代码审查时为你节省时间。
7.1 忘记排序是破坏性的
Both list.sort() and Collections.sort() 会修改原始 List.
List<Integer> original = new ArrayList<>(List.of(3, 1, 2));
List<Integer> alias = original;
original.sort(Comparator.naturalOrder());
在这种情况下:
original已排序alias也已排序(因为它们引用同一个 List)
如何避免这种情况
如果需要保留原始顺序:
List<Integer> sorted = new ArrayList<>(original);
sorted.sort(Comparator.naturalOrder());
或者使用流:
List<Integer> sorted =
original.stream()
.sorted()
.toList();
时刻自问:
“修改原始 List 可以吗?”
7.2 Comparator 一致性与稳定排序
Comparator 应该产生 一致且可预测的结果。
示例:
Comparator.comparing(Person::getAge);
如果多个人的年龄相同,它们的相对顺序是未定义的。
这在某些情况下可以接受——但通常不行。
最佳实践
添加次要条件以稳定顺序:
Comparator.comparingInt(Person::getAge)
.thenComparing(Person::getId);
这确保排序结果是确定的。
7.3 字符串排序中的大小写敏感性
String 的自然顺序是 区分大小写 的。
List<String> list = List.of("apple", "Banana", "orange");
list.sort(Comparator.naturalOrder());
这可能产生不直观的结果。
不区分大小写的排序
list.sort(String.CASE_INSENSITIVE_ORDER);
在选择之前,考虑:
- 这用于显示吗?
- 还是用于内部逻辑?
答案决定了正确的做法。
7.4 在 Comparator 中进行繁重工作
在排序过程中,Comparator 可能被 多次 调用。
避免:
- 数据库访问
- 网络调用
- 高开销的计算
// Bad idea (conceptual example) Comparator.comparing(p -> expensiveOperation(p));
更好的做法
- 预先计算值
- 将其存入字段
- 比较简单、低成本的值
高效的 Comparator 在处理大 List 时差别巨大。
7.5 优先考虑可读性而非巧妙
排序逻辑往往被阅读的次数多于编写的次数。
而不是:
- 一个冗长的链式表达式
- 深度嵌套的 lambda
更倾向于:
- 换行
- 清晰的结构
- 可选的业务规则注释
可读的排序代码能减少 bug 并使维护更容易。
8. 性能考虑与正确方法的选择
到目前为止,你已经了解了在 Java 中 如何 对 List 进行排序。
本节侧重于从性能和设计角度 选择哪种方法。
在大多数应用中,排序不是瓶颈——但不当的选择仍可能导致不必要的开销。
8.1 list.sort() 与 stream().sorted()
这是最常见的决策点。
list.sort()
list.sort(Comparator.comparingInt(Person::getAge));
优点
- 没有额外的 List 分配
- 意图明确:“对该 List 排序”
- 稍微更高效
缺点
- 会修改原始 List
stream().sorted()
List<Person> sorted =
list.stream()
.sorted(Comparator.comparingInt(Person::getAge))
.toList();
优点
- 原始 List 保持不变
- 自然地融入流管道
缺点
- 会分配一个新 List
- 稍有额外开销
实用规则
- 简单排序 →
list.sort() - 转换管道或需要不可变 →
stream().sorted()
8.2 高效排序大 List
排序算法会多次调用 Comparator 很多次。 对于大 List,这一点很重要。
关键指南
- 保持 Comparator 轻量
- 避免进行繁重工作的链式调用
- 优先使用原始类型 Comparator(
comparingInt等)
示例:预先计算昂贵的键
而不是:
Comparator.comparing(p -> calculateScore(p));
这样做:
// Precompute once
p.setScore(calculateScore(p));
然后按字段排序:
Comparator.comparingInt(Person::getScore);
这大大减少了排序过程中的重复工作。
8.3 Collections.sort() 是否曾经是正确选择?
对于新代码,几乎从不。
然而,它仍然出现在:
- 遗留项目
- 较旧的教程
- Java 7 及更早的代码库
你不需要使用它——但你应该认识它。
8.4 推荐决策检查清单
在排序之前,问自己:
- 我可以修改原始 List 吗?
- 我需要多个排序条件吗?
- 任何字段可以是
null吗? - 性能在大规模时重要吗?
回答这些问题自然会引导你到正确的解决方案。
9. 总结:Java List 排序速查表
让我们将所有内容总结成一个快速参考指南,你可以依赖它。
9.1 你最常使用的快速模式
升序
list.sort(Comparator.naturalOrder());
降序
list.sort(Comparator.reverseOrder());
9.2 按字段排序对象
list.sort(Comparator.comparing(Person::getName));
对于数字:
list.sort(Comparator.comparingInt(Person::getAge));
9.3 多个条件
list.sort(
Comparator.comparingInt(Person::getAge)
.thenComparing(Person::getName)
);
混合顺序:
list.sort(
Comparator.comparingInt(Person::getScore).reversed()
.thenComparing(Person::getName)
);
9.4 安全的 null 处理
list.sort(
Comparator.comparing(
Person::getName,
Comparator.nullsLast(Comparator.naturalOrder())
)
);
List 可能包含 null 元素:
list.sort(
Comparator.nullsLast(
Comparator.comparing(Person::getName)
)
);
9.5 非破坏性排序
List<Person> sorted =
list.stream()
.sorted(Comparator.comparingInt(Person::getAge))
.toList();
9.6 最终要点
一旦你记住以下内容,
Java List 排序就会变得简单:
Comparator定义顺序sort()执行操作- 清晰胜过巧妙
- 显式
null处理防止 bug
如果你内化这些原则,
你就再也不需要“重新学习” Java List 排序了。

