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()会把名称为nullStudent对象放在最前面,但是调用reverse()之后,Student对象按照名字降序输出,同时把null对象放在最后。

如何在调用reverse()的时候仍把null对象放在最前面呢?

  1. Ordering.natural().reverse().nullsFirst().onResultOf(sortFunction);
  2. 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}
 */

参考

posted @ 2020-06-03 10:06  weegee  阅读(309)  评论(0编辑  收藏  举报