摘要:介绍使用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(类::属性一));
标签:Java,stream,Comparator,list,comparing,sorted,排序,属性
From: https://www.cnblogs.com/fuqian/p/18131487