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<UserDTO> {

    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<T> 
static <T,U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T,? extends U> keyExtractor) 

// 从T类型对象提取U类型的排序字段,并返回一个根据此排序字段Comparator<T> 
static <T,U> Comparator<T> comparing(Function<? super T,? extends U> keyExtractor, Comparator<? super U> keyComparator) 

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

Function<UserDTO, Integer> 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<T> thenComparing(Comparator<? super T> other) 
// 返回指定属性的 Comparable顺序比较器
default <U extends Comparable<? super U>> Comparator<T> thenComparing(Function<? super T,? extends U> keyExtractor) 
// 返回一个根据T对象U类型字段字段排序的Comparator
default <U> Comparator<T> thenComparing(Function<? super T,? extends U> keyExtractor, Comparator<? super U> keyComparator) 

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

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

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

List<UserDTO> 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<UserDTO> 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。如果为null可以设置一个默认值,或者使用如下方法解决:

//值为null的元素排在前面
Comparator<类> firstComparator = Comparator.nullsFirst(Comparator.comparing(类::属性一));

//所有的空元素将被排在最后,不影响非空元素排序
Comparator<类> lastComparator = Comparator.nullsLast(Comparator.comparing(类::属性一));

Reference

posted @ 2023-03-05 15:37  楼兰胡杨  阅读(8605)  评论(1编辑  收藏  举报