zoukankan      html  css  js  c++  java
  • 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}
     */
    

    参考

  • 相关阅读:
    查看full gc频率怎么样
    【LeetCode每天一题】Linked List Cycle II(循环链表II)
    【LeetCode每天一题】Word Break()
    【LeetCode每天一题】Candy(分糖果)
    【LeetCode每天一题】Single Number II(数组中单个数字II)
    【LeetCode每天一题】Gas Station(汽油站)
    【LeetCode每天一题】Single Number(数组中单独的数字)
    【LeetCode每天一题】Sum Root to Leaf Numbers(二叉树所有根到叶节点之和)
    【LeetCode每天一题】Longest Consecutive Sequence(最长的连续序列
    【LeetCode每天一题】 Word Ladder(单词阶梯)
  • 原文地址:https://www.cnblogs.com/weegee/p/13035692.html
Copyright © 2011-2022 走看看