• Java stream sorted使用 Comparator 进行多字段排序


    摘要:介绍使用Java Stream流排序器Comparator对List集合进行多字段排序的方法,包括复杂实体对象多字段升降序排序方法。

    综述

      Java 8 的 Stream 使用了函数式编程模式,人如其名,它可以被用来对集合或数组进行链状流式的排序、过滤和统计等操作,从而让我们更方便的对集合或数组进行操作。

      关于List排序,工作中,一般使用SQL中的order by进行排序,但有时候使用Java代码进行排序,例如合并多个list对象的数据后,以年龄降序排列,这显然是无法通过SQL语句搞定的,而一般的冒泡排序、希尔排序等需要手写实现,容易出错,而且代码量大,测试工作量自然不容小觑。这时,就需要搬出Stream sort方法进行排序,重写其中的Comparator。

      本文重点介绍使用Java Stream流排序器Comparator对List集合进行排序的技巧,包括复杂实体对象多字段升降序排序方法。

    重写类的Comparable接口

      重写List中泛型Bean的compareTo方法实现排序,即流中泛型元素需实现Comparable接口,实现如下:

    
    import lombok.Getter;
    import lombok.Setter;
    import lombok.ToString;
    
    import java.io.Serializable;
    
    /**
     * 用户实体
     */
    @Getter
    @Setter
    @ToString
    public class UserDTO implements Serializable, Comparable {
    
        private static final long serialVersionUID = -2618535482684811077L;
        /**
         * 用户id
         */
        private Long id;
    
        /**
         * 姓名
         */
        private String name;
        /**
         * 年龄
         */
        private Integer age;
    
        /**
         * 是否男士,true 是
         */
        private Boolean isBoy;
        /**
         * 无参构造器
         */
        public UserDTO() {
            System.out.println("无参构造器");
        }
    
        private UserDTO(String userName) {
            this.name = userName;
            System.out.println("一个参数的构造器,private");
        }
    
        public UserDTO(Long id, String userName, Integer age) {
            this.id = id;
            this.name = userName;
            this.age = age;
        }
        public UserDTO(Long id, String userName, Integer age,  Boolean isBoy) {
            this.id = id;
            this.name = userName;
            this.age = age;
            this.isBoy = isBoy;
        }
    
        @Override
        public int compareTo(UserDTO o) {
            return id.compareTo(o.getId());
        }
    }
    

      缺点是所有类都会使用这个排序规则,不适用于排序规则灵活多变的复杂业务场景。

    使用Comparator排序

      使用stream的sorted(Comparator com)基于自定义规则排序,这需要为comparing 和thenComparing自定义Comparator排序器,以实现升序或者降序。接下来进行案例分析的时候,默认UserDTO没有重写类的Comparable接口。

    sorted comparing 自然排序

      sorted 排序结果默认升序排序,它根据comparing来实现。语法糖:

    // 从类型T中提取Comparable排序属性,并返回该属性的比较器Comparator 
    static extends Comparablesuper U>> Comparator comparing(Functionsuper T,? extends U> keyExtractor) 
    
    // 从T类型对象提取U类型的排序字段,并返回一个根据此排序字段Comparator 
    static  Comparator comparing(Functionsuper T,? extends U> keyExtractor, Comparatorsuper U> keyComparator) 
    

       Function 是一个函数接口,包含一种 apply()方法,来实现方法调用。参数Function keyExtractor表示输入一个 T 类型形参,输出一个 U 类型的对象。举个例子,输入一个 UserDTO 对象返回其Integer类型属性年龄(age)的数值:

    Function getUserAge = UserDTO::getAge;
    

    使用默认属性排序:

    list = list.stream().sorted().collect(Collectors.toList());
    

      下面是根据年龄升序排序的示例:

    list = list.stream().sorted(Comparator.comparing(UserDTO::getAge))
    .collect(Collectors.toList());
    

      如果想实现降序排列,可以使用Comparator 提供的reverseOrder() 方法

    list = list.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());
    

      下面是根据年龄降序排列的示例:

    list = list.stream().sorted(Comparator.comparing(UserDTO::getAge).reversed())
    .collect(Collectors.toList());
    
    or
    
    list = list.stream().sorted(Comparator.comparing(UserDTO::getAge, Comparator.reverseOrder()))
    .collect(Collectors.toList());
    

      像Integer、Long等基本类型的包装类已经实现了Comparable接口,在使用sorted排序的时候,可以使用comparingInt、thenComparingInt、thenComparingLong等。

    thenComparing 多字段排序

      在多于一个属性排序的场景,可以结合 comparing 和 thenComparing进行解决——先使用comparing进行比较,再使用一个或者多个thenComparing进行排序。语法糖:

    //用另一个比较器 other 返回一个字典顺序排序比较器
    default Comparator thenComparing(Comparatorsuper T> other) 
    // 返回指定属性的 Comparable顺序比较器
    default extends Comparablesuper U>> Comparator thenComparing(Functionsuper T,? extends U> keyExtractor) 
    // 返回一个根据T对象U类型字段字段排序的Comparator
    default  Comparator thenComparing(Functionsuper T,? extends U> keyExtractor, Comparatorsuper U> keyComparator) 
    

      案例1:集合以泛型类的属性一升序、属性二升序排序:

    Comparator<类> comparator = Comparator.comparing(类::属性一).thenComparing(类::属性二);
    list=list.stream().sorted(comparator).collect(Collectors.toList());
    

      案例2:按用户年龄升序,年龄相同时则按姓名升序:

    List sortedList=list.sorted(Comparator.comparing(UserDTO::getAge).thenComparing(UserDTO::getName))
    .collect(Collectors.toList());
    sortedList.stream().forEach(System.out::println);
    

      案例3:排序结果以属性一降序,属性二升序排列:

    Comparator<类> comparator = Comparator.comparing(类::属性一,Comparator.reverseOrder()).thenComparing(类::属性二);
    list=list.stream().sorted(comparator).collect(Collectors.toList());
    

      这里自定义了一个比较器对象,修改对象排序规则即可。如果某个属性需要降序,则在comparing中声明Comparator.reverseOrder(),例如:

    Comparator comparator = Comparator.comparing(UserDTO::getAge, Comparator.reverseOrder()).thenComparing(UserDTO::getName)
    list=list.sorted(comparator).collect(Collectors.toList());
    

      当然了,也可以把Comparator.reverseOrder()放到属性二的位置,此时表示以属性一升序、属性二降序排列:

    list=list.stream().sorted(Comparator.comparing(类::属性一).thenComparing(类::属性二,Comparator.reverseOrder()))
      .collect(Collectors.toList());
    

    注意事项

      1、降序排列时,只需要在 comparator 末尾写一个 reversed(),不需要每个比较属性都写

    Comparator<类> comparator1 =
     Comparator.comparing(类::属性一).thenComparing(类::属性二).reversed();
    

      但是,不建议这样写,推荐如下语义更清晰的语法糖:

    Comparator<类> comparator1 = Comparator.comparing(类::属性一, Comparator.reverseOrder()).thenComparing(类::属性二, Comparator.reverseOrder())
    

      2、构建比较器时如果分多行,不能以如下形式定义,否则会排序不正确:

    Comparator<类> comparator2 = Comparator.comparing(类::属性一);
    comparator2.thenComparing(类::属性二);
    

    但可以写成

    Comparator<类> comparator2 = Comparator.comparing(类::属性一);
    comparator2 = comparator2.thenComparing(类::属性二);
    

      3、sorted()方法返回的结果集是一个新的对象,和被排序对象的引用不一样。

      4、进行排序的类属性不可为null。

    Reference

  • 相关阅读:
    Word文件损坏怎么办?这3个方法教你轻松解决!
    数据链路层——MAC地址欺骗及泛洪
    什么是本地存储的有效期?
    c++: 引用能否替代指针? 详解引用与指针的区别.
    【机器学习基础】对数几率回归(logistic回归)
    电容笔和触屏笔一样吗?高性价比电容笔排行
    学生考试作弊检测系统 yolov8
    JDBC 在性能测试中的应用
    用Unity重现《空洞骑士》的苦痛之路(1)——人物控制篇
    性能优越!|融合多策略的灰狼优化算法
  • 原文地址:https://www.cnblogs.com/east7/p/17180704.html