在 Java 开发中,对集合进行排序是非常常见的需求。虽然 Java 提供了 Comparator 和 Comparable 接口,但在实现复杂的排序逻辑(如空值处理、多条件排序、逆序等)时,代码往往会变得冗长且容易出错。
Google Guava 库中的 Ordering 工具类为我们提供了一种流畅、强大且易于阅读 的方式来构建复杂的排序规则。它本质上是 Comparator 的增强实现,并添加了许多实用的方法。
本文将带你全面掌握 Guava Ordering 的使用,让你的排序代码从此变得优雅起来。
一、为什么需要 Ordering?
我们先来看一个原生 Java 排序的例子:对一个字符串列表进行排序,要求将 null 值放在最后,并且忽略大小写。
Java 原生实现 (略显繁琐):
java
List<String> list = Arrays.asList("banana", null, "apple", "Cat", null, "dog");
list.sort(new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
// 处理 null 值:null 被视为更大,放在最后
if (s1 == null && s2 == null) return 0;
if (s1 == null) return 1;
if (s2 == null) return -1;
// 非 null 值,忽略大小写比较
return s1.compareToIgnoreCase(s2);
}
});
System.out.println(list);
// 输出: [apple, banana, Cat, dog, null, null]
这段代码功能是实现了,但比较逻辑和空值处理混杂在一起,可读性和复用性都较差。
Guava Ordering 实现 (清晰流畅):
java
import com.google.common.collect.Ordering;
List<String> list = Arrays.asList("banana", null, "apple", "Cat", null, "dog");
Ordering<String> ordering = Ordering
.from(String.CASE_INSENSITIVE_ORDER) // 1. 基于忽略大小写的比较器
.nullsLast(); // 2. 组合:null 值放最后
Collections.sort(list, ordering);
// 或者使用 list.sort(ordering);
System.out.println(list);
// 输出: [apple, banana, Cat, dog, null, null]
通过链式调用,Ordering 让排序规则的构建变得一目了然,并且每个步骤(基础比较、空值处理)都是独立的、可复用的。
二、创建 Ordering 的几种方式
Ordering 的创建非常灵活,主要有以下几种方式:
-
使用自然排序
适用于实现了
Comparable接口的类型。javaOrdering<String> naturalOrdering = Ordering.natural(); // 等同于 Comparator.naturalOrder() -
使用
from()方法包装已有的Comparator这是最常用的方式,可以将任何
Comparator转换为功能更强大的Ordering。javaOrdering<String> byLength = Ordering.from(Comparator.comparingInt(String::length)); -
使用显式顺序
如果你想按照一个预定义的列表顺序来排序对象,可以使用
explicit()。java// 按照 "LOW", "MEDIUM", "HIGH" 的顺序排序 Ordering<String> severityOrdering = Ordering.explicit("LOW", "MEDIUM", "HIGH"); List<String> priorities = Arrays.asList("HIGH", "LOW", "MEDIUM"); Collections.sort(priorities, severityOrdering); System.out.println(priorities); // 输出: [LOW, MEDIUM, HIGH]注意:待排序列表中的元素必须全部存在于显式列表中。
-
使用字符串特定排序
提供了基于字符顺序、数字等的排序,例如:
java// 使用字典序(字符的 Unicode 值) Ordering<Object> usingToStringOrdering = Ordering.usingToString();
三、核心组合操作:链式调用之美
Ordering 的真正威力在于它可以进行链式调用来组合复杂的排序策略。这些方法返回一个新的 Ordering,不会修改原对象。
-
nullsFirst()/nullsLast(): 指定null值出现在排序结果的开头或结尾。javaOrdering<String> withNullsFirst = Ordering.natural().nullsFirst(); -
reverse(): 反转当前排序规则。javaOrdering<String> reversedNatural = Ordering.natural().reverse(); // 降序 -
compound(Comparator): 用于实现多级排序。当第一个比较器结果相同时,使用第二个比较器进行判断。java// 假设有一个 Person 类,有 lastName 和 firstName 属性 Ordering<Person> ordering = Ordering .from(Comparator.comparing(Person::getLastName)) .compound(Comparator.comparing(Person::getFirstName)); // 先按姓排序,姓相同再按名排序 -
onResultOf(Function): 这是一个非常灵活的方法。它先通过一个Function将原对象转换为某个值,然后根据这个值的自然顺序(或指定的Ordering)进行排序。java// 按字符串长度排序 Ordering<String> byLength = Ordering.natural().onResultOf(String::length); // 更复杂的例子:按日期排序,但对象本身是 Event 类型 // Ordering<Event> byEventDate = Ordering.natural().onResultOf(Event::getDate);
现在,我们可以用 onResultOf 组合出更强大的排序链:
java
// 排序规则:按字符串长度排序,长度相同的按自然顺序(字母序)排序,null 值放最后
Ordering<String> complexOrdering = Ordering
.natural() // 基础规则:自然排序
.nullsLast() // 规则3: null 放最后
.onResultOf(String::length) // 规则1: 先按长度映射
.compound(Ordering.natural()); // 规则2: 长度相同则按自然顺序
四、实用的集合操作方法
Ordering 不仅可以用作排序规则,其本身还提供了许多操作集合的便捷方法。
java
Ordering<String> ordering = Ordering.natural().nullsLast();
List<String> list = Arrays.asList("banana", "apple", null, "cat");
// 1. 检查集合是否已经按照此 Ordering 排序
boolean isOrdered = ordering.isOrdered(list); // false,因为 null 在中间
boolean isStrictlyOrdered = ordering.isStrictlyOrdered(list); // false
// 2. 获取排序后的最小/最大元素
List<String> sortedCopy = ordering.sortedCopy(list);
// sortedCopy: [apple, banana, cat, null]
String min = ordering.min(list); // "apple"
String max = ordering.max(list); // null
// 3. 获取最小的 k 个元素
List<String> leastK = ordering.leastOf(list, 2);
// leastK: [apple, banana] (最小的两个)
// 4. 获取最大的 k 个元素
List<String> greatestK = ordering.greatestOf(list, 2);
// greatestK: [cat, null] (最大的两个)
这些方法内部已经处理好了迭代和比较逻辑,极大地简化了代码。
五、与 Java 8+ Comparator 的对比
Java 8 引入的 Lambda 表达式和 Comparator 的默认方法也支持了类似的链式调用。那么,有了 Java 8 的 Comparator,我们还需要 Guava Ordering 吗?
Java 8 Comparator 实现相同功能:
java
List<String> list = Arrays.asList("banana", null, "apple", "Cat", null, "dog");
Comparator<String> comparator = Comparator
.comparingInt(String::length) // 先按长度
.thenComparing(String.CASE_INSENSITIVE_ORDER) // 再按忽略大小写
.thenComparing(Comparator.nullsLast(Comparator.naturalOrder())); // null 处理
list.sort(comparator);
对比与选择:
| 特性 | Guava Ordering |
Java 8 Comparator |
结论 |
|---|---|---|---|
| 链式调用 | 支持,非常成熟 | 支持,功能强大 | 两者都支持 |
| 空值处理 | nullsFirst() / nullsLast() |
nullsFirst() / nullsLast() 静态方法 |
功能等价 |
| 集合操作 | 内置 min()、max()、leastOf() 等方法 |
需要借助 Collections 或 Stream |
Guava 更便捷 |
| 多级排序 | compound() |
thenComparing() |
功能等价 |
| 映射排序 | onResultOf() |
comparing() |
功能等价 |
| 项目依赖 | 需要引入 Guava | 原生支持 | Java 8+ 无依赖 |
选择建议:
- 如果你的项目已经使用了 Guava ,或者你需要像
leastOf()、isOrdered()这样便捷的集合操作方法,那么Ordering依然是一个很好的选择。 - 如果你希望减少外部依赖 ,或者主要进行简单的链式排序,Java 8 的
Comparator已经完全足够,并且是现代 Java 应用的首选。
六、总结
Google Guava 的 Ordering 工具类是对 Java Comparator 的一次优雅增强。它通过流畅的链式调用,让复杂的排序逻辑变得直观、可读且易于维护。
- 它简化了空值处理 和多级排序。
- 它提供了基于函数映射 (
onResultOf) 的灵活排序能力。 - 它内置了
min()、max()、sortedCopy()等便捷的集合操作方法。
即使在与 Java 8 Comparator 的对比中,Ordering 凭借其丰富的 API 和集合操作支持,在特定场景下仍有其独特的价值。下次当你遇到复杂的排序需求时,不妨试试 Guava Ordering,感受它带来的简洁与高效。
希望这篇博客能帮助你全面掌握 Guava Ordering 的使用!