Google Guava之Ordering
文中所述Guava版本基于29.0-jre,文中涉及到的代码完整示例请移步Github查看。
概述
Guava的Ordering是一种特殊的比较器,和JDK的Comparator相比较,它提供了更多的功能。
从实现上说,Ordering实例就是一个特殊的Comparator实例。Ordering把很多基于Comparator的静态方法(如Collections.max)包装为自己的实例方法(非静态方法),并且提供了链式调用方法,来定制和增强现有的比较器。
JDK比较器
在使用JDK的时候,要是需要比较两个对象的大小关系,一般有两种办法,一是继承Comparable接口并实现
compareTo(T o);方法,另外一种是创建Comparator类。
下面创建Student类,并以此作为比较对象来观察如何在JDK中比较对象。
继承Comparable接口
public class Student implements Comparable<Student> {
private String name;
private Integer grade;
@Override
public int compareTo(@Nullable Student other) {
Preconditions.checkNotNull(other);
return this.grade - other.grade;
}
}
首先创建几个学生
List<Student> studentList = new ArrayList<>();
studentList.add(new Student("Alice", 88));
studentList.add(new Student("Bob", 92));
studentList.add(new Student("Ceb", 70));
studentList.add(new Student("David", 66));
studentList.add(new Student("Benjamin", 90));
根据学生得分从低到高输出学生信息
Collections.sort(studentList);
// output:
/*
Student{name='David', grade=66}
Student{name='Ceb', grade=70}
Student{name='Alice', grade=88}
Student{name='Benjamin', grade=90}
Student{name='Bob', grade=92}
*/
Comparator类
根据学生名字自然顺序创建比较器
Comparator<Student> nameCompare = new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getName().compareTo(o2.getName());
}
};
输出学生信息
Collections.sort(studentList, nameCompare);
or
studentList.sort(nameCompare);
// output:
/*
Student{name='Alice', grade=88}
Student{name='Benjamin', grade=90}
Student{name='Bob', grade=92}
Student{name='Ceb', grade=70}
Student{name='David', grade=66}
*/
Ordering比较器
创建比较器
Guava Ordering提供了几种静态方法来创建排序器
| 方法 | 描述 |
|---|---|
natural() |
对可排序类型做自然排序,如数字按大小,日期按先后排序(字符串时按字典顺序,和usingToString()等价) |
usingToString() |
按字符串的字典顺序排序 |
from(Comparator<T> comparator) |
从JDK的Comparator创建排序器 |
explicit(List<T> valuesInOrder) |
创建时显示指定顺序 |
allEqual() |
创建所有项都相等的排序器 |
arbitrary() |
创建随机顺序的排序器 |
创建Ordering的方法和JDK的创建Comparator的方法几乎一样
Ordering<PureStudent> ordering = new Ordering<PureStudent>() {
@Override
public int compare(@Nullable PureStudent s1, @Nullable PureStudent s2) {
return s1.getName().compareTo(s2.getName());
}
};
List<PureStudent> pureStudents = generateStudent();
pureStudents.sort(ordering);
print(pureStudents);
下面介绍如何使用Ordering提供静态方法创建比较器,代码中统一使用的方法如下
private void print(List<PureStudent> studentList) {
Preconditions.checkNotNull(studentList);
for (PureStudent student : studentList) {
System.out.println(student.toString());
}
}
private Function sortFunction = new Function<PureStudent, Comparable>() {
@Nullable
@Override
public Comparable apply(@Nullable PureStudent pureStudent) {
return pureStudent.getName();
}
};
private List<PureStudent> generateStudent() {
List<PureStudent> studentList = new ArrayList<>();
studentList.add(new PureStudent("Alice", 88));
studentList.add(new PureStudent("Bob", 92));
studentList.add(new PureStudent("Ceb", 70));
studentList.add(new PureStudent("David", 66));
studentList.add(new PureStudent("Benjamin", 90));
studentList.add(new PureStudent("David", 63));
return studentList;
}
natural()
创建自然顺序的排序器(此处输出字符串的字典顺序,字符串的自然顺序即为字典顺序)
List<PureStudent> pureStudents = generateStudent();
Ordering<PureStudent> ordering = Ordering.natural().onResultOf(sortFunction);
pureStudents.sort(ordering);
print(pureStudents);
// output:
/*
Student{name='Alice', grade=88}
Student{name='Benjamin', grade=90}
Student{name='Bob', grade=92}
Student{name='Ceb', grade=70}
Student{name='David', grade=66}
Student{name='David', grade=63}
*/
如输出所示,排序后的Student对象按照名字的自然顺序输出。
usingToString()
创建字符串字典顺序的排序器
List<PureStudent> pureStudents = generateStudent();
Ordering<PureStudent> ordering = Ordering.usingToString().onResultOf(sortFunction);
pureStudents.sort(ordering);
print(pureStudents);
// output:
/*
Student{name='Alice', grade=88}
Student{name='Benjamin', grade=90}
Student{name='Bob', grade=92}
Student{name='Ceb', grade=70}
Student{name='David', grade=66}
Student{name='David', grade=63}
*/
allEqual()
创建所有对象相等的排序器
List<PureStudent> pureStudents = generateStudent();
Ordering<PureStudent> ordering = Ordering.allEqual().onResultOf(sortFunction);
pureStudents.sort(ordering);
print(pureStudents);
// output:
/*
Student{name='Alice', grade=88}
Student{name='Bob', grade=92}
Student{name='Ceb', grade=70}
Student{name='David', grade=66}
Student{name='Benjamin', grade=90}
Student{name='David', grade=63}
*/
由于默认所有对象相等,输出顺序就是对象创建的顺序。
arbitrary()
创建随机顺序的排序器
List<PureStudent> pureStudents = generateStudent();
Ordering<PureStudent> ordering = Ordering.arbitrary().onResultOf(sortFunction);
pureStudents.sort(ordering);
print(pureStudents);
// output:
/*
Student{name='David', grade=66}
Student{name='David', grade=63}
Student{name='Alice', grade=88}
Student{name='Ceb', grade=70}
Student{name='Benjamin', grade=90}
Student{name='Bob', grade=92}
*/
输出的顺序为随机顺序(多次运行可能会有不同顺序的输出)。
from(Comparator comparator)
使用现有的比较器创建排序器
Comparator<PureStudent> nameComparator = new Comparator<PureStudent>() {
@Override
public int compare(PureStudent o1, PureStudent o2) {
return o2.getName().compareTo(o1.getName());
}
};
List<PureStudent> pureStudents = generateStudent();
Ordering<PureStudent> ordering = Ordering.from(nameComparator);
pureStudents.sort(ordering);
print(pureStudents);
// output:
/*
Student{name='David', grade=66}
Student{name='David', grade=63}
Student{name='Ceb', grade=70}
Student{name='Bob', grade=92}
Student{name='Benjamin', grade=90}
Student{name='Alice', grade=88}
*/
使用字符串降序比较器Comparator创建排序器。
explicit()
根据显式指定的顺序创建比较器
List<PureStudent> studentList = generateStudent();
List<String> stringList = Arrays.asList("Alice", "David", "Benjamin", "Ceb", "Bob");
Ordering<PureStudent> explicitOrdering = Ordering.explicit(stringList).onResultOf(sortFunction);
studentList.sort(explicitOrdering);
print(studentList);
// output:
/*
Student{name='Alice', grade=88}
Student{name='David', grade=66}
Student{name='David', grade=63}
Student{name='Benjamin', grade=90}
Student{name='Ceb', grade=70}
Student{name='Bob', grade=92}
*/
创建显式顺序的列表Arrays.asList("Alice", "David", "Benjamin", "Ceb", "Bob"),然后排序,排序后的顺序和显式列表顺序一致。
链式Ordering
Ordering的特别之处不仅在于它可以提供多种创建排序器的静态方法,同时它还支持链式操作,可以很大的提升排序能力。
我们在前面的内容中多次看到使用onResultOf(),这就属于Ordering的链式调用,意思是把排序器的规则应用到哪个字段上。
除此之外,还会在链式调用中经常用到的方法有
| 方法 | 描述 |
|---|---|
reverse() |
获取语义相反的排序器 |
nullsFirst() |
使用当前排序器,但额外把null值排到最前面。 |
nullsLast() |
使用当前排序器,但额外把null值排到最后面。 |
compound(Comparator) |
合成另一个比较器,以处理当前排序器中的相等情况。 |
lexicographical() |
基于处理类型T的排序器,返回该类型的可迭代对象Iterable<T>的排序器。 |
onResultOf(Function) |
对集合中元素调用Function,再按返回值用当前排序器排序。 |
reverse()和nullsFirst()
普通的排序器遇到null值的情况处理起来会比较麻烦,而在链式调用中采用nullsFirst()或者nullsLast()就可以完全避免处理null的额外操作。它会根据要求把null的值放在最前面或者最后面。
List<PureStudent> studentList = generateStudent();
studentList.add(new PureStudent(null, 70));
Ordering<PureStudent> ordering = Ordering.natural().nullsFirst().reverse().onResultOf(sortFunction);
studentList.sort(ordering);
print(studentList);
// output: nullsFirst().reverse()
/*
Student{name='David', grade=66}
Student{name='David', grade=63}
Student{name='Ceb', grade=70}
Student{name='Bob', grade=92}
Student{name='Benjamin', grade=90}
Student{name='Alice', grade=88}
Student{name='null', grade=70}
*/
按照nullsFirst().reverse()的顺序进行链式调用,首先nullsFirst()会把名称为null的Student对象放在最前面,但是调用reverse()之后,Student对象按照名字降序输出,同时把null对象放在最后。
如何在调用reverse()的时候仍把null对象放在最前面呢?
Ordering.natural().reverse().nullsFirst().onResultOf(sortFunction);Ordering.natural().nullsLast().reverse().onResultOf(sortFunction);
可以看到,在书写调用链的时候,是从左向右书写,但是在阅读调用链的时候建议先阅读onResultOf(),然后阅读通过静态方法创建的排序器(例如natural()),最后从左到右阅读排序器和onResultOf()中间的部分。比如
Ordering.natural().reverse().nullsFirst().onResultOf(sortFunction),读起来应该是通过sortFunction取出Student对象的name字段,然后按照自然顺序排序,再把null元素放在最后,然后反转所有元素(包含null)。
compound()
之前可以看到创建的Student对象有两个名称都为David的对象,但是他们的分数不一样,针对同名的Student如果想按照分数升序排序,可以创建一个比较器然后合成。
private Comparator<PureStudent> gradeComparator = new Comparator<PureStudent>() {
@Override
public int compare(PureStudent o1, PureStudent o2) {
return o1.getGrade() - o2.getGrade();
}
};
List<PureStudent> pureStudents = generateStudent();
Ordering<PureStudent> ordering = Ordering.natural().onResultOf(sortFunction).compound(gradeComparator);
pureStudents.sort(ordering);
print(pureStudents);
// output:
/*
Student{name='Alice', grade=88}
Student{name='Benjamin', grade=90}
Student{name='Bob', grade=92}
Student{name='Ceb', grade=70}
Student{name='David', grade=63}
Student{name='David', grade=66}
*/
阅读带有compound()的调用链时,应该把其它的排序器和compound()的功能当作独立的功能,其它的排序器处理后的结果再交给compound()处理(或者compound()处理后再交给其它的排序器处理)。同时为了避免理解上的混乱,请不要把compound写在一长串链式调用的中间,你可以另起一行,在链中最先或最后调用compound()。
其他的方法
| 方法 | 描述 | 另请参见 |
|---|---|---|
greatestOf(Iterable iterable, int k) |
获取可迭代对象中最大的k个元素。 | leastOf |
isOrdered(Iterable) |
判断可迭代对象是否已按排序器排序:允许有排序值相等的元素。 | isStrictlyOrdered |
sortedCopy(Iterable) |
判断可迭代对象是否已严格按排序器排序:不允许排序值相等的元素。 | immutableSortedCopy |
min(E, E) |
返回两个参数中最小的那个。如果相等,则返回第一个参数。 | max(E, E) |
min(E, E, E, E...) |
返回多个参数中最小的那个。如果有超过一个参数都最小,则返回第一个最小的参数。 | max(E, E, E, E...) |
min(Iterable) |
返回迭代器中最小的元素。如果可迭代对象中没有元素,则抛出NoSuchElementException。 | max(Iterable), min(Iterator), max(Iterator) |
在排序好的列表中输出最大的两个元素
List<PureStudent> pureStudents = generateStudent();
List<PureStudent> topList = Ordering.natural().onResultOf(sortFunction).greatestOf(pureStudents.iterator(), 2);
print(topList);
//output:
/*
Student{name='David', grade=66}
Student{name='David', grade=63}
*/

浙公网安备 33010602011771号