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}
*/