首页 > 编程语言 >Java stream sorted使用 Comparator 进行多字段排序

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

时间:2024-04-12 15:56:25浏览次数:19  
标签:Java stream Comparator list comparing sorted 排序 属性

摘要:介绍使用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

相关文章

  • Java 中文官方教程 2022 版(四十一)
    原文:docs.oracle.com/javase/tutorial/reallybigindex.html错误处理原文:docs.oracle.com/javase/tutorial/jaxp/limits/error.html建议应用程序在设置新属性时捕获org.xml.sax.SAXNotRecognizedException异常,以便应用程序在不支持这些属性的旧版本上正常工作。例如,可下载的......
  • Java 中文官方教程 2022 版(四十二)
    原文:docs.oracle.com/javase/tutorial/reallybigindex.html设置策略文件以授予所需的权限。原文:docs.oracle.com/javase/tutorial/security/toolsign/rstep3.html接下来,您将使用策略工具创建一个名为exampleraypolicy的策略文件,并在其中授予来自已签名JAR文件的代码权限......
  • Java 中文官方教程 2022 版(四十四)
    原文:docs.oracle.com/javase/tutorial/reallybigindex.html调用方法原文:docs.oracle.com/javase/tutorial/reflect/member/methodInvocation.html反射提供了一种在类上调用方法的方式。通常,只有在非反射代码中无法将类的实例强制转换为所需类型时才需要这样做。方法是使用j......
  • Java 中文官方教程 2022 版(四十五)
    原文:docs.oracle.com/javase/tutorial/reallybigindex.html教程:自定义网络原文:docs.oracle.com/javase/tutorial/networking/index.htmlJava平台备受推崇,部分原因是其适用于编写使用和与互联网资源以及万维网进行交互的程序。事实上,兼容Java的浏览器极大地利用了Java......
  • Java 中文官方教程 2022 版(四十六)
    原文:docs.oracle.com/javase/tutorial/reallybigindex.html定义简单的通用类型原文:docs.oracle.com/javase/tutorial/extra/generics/simple.html这里是包java.util中接口List和Iterator的定义的一个小节选:publicinterfaceList<E>{voidadd(Ex);Iterator<E......
  • Java 中文官方教程 2022 版(四十九)
    原文:docs.oracle.com/javase/tutorial/reallybigindex.htmlJAXB示例原文:docs.oracle.com/javase/tutorial/jaxb/intro/examples.html以下部分描述如何使用包含在JAXBRI捆绑包中的示例应用程序。JAXBRI捆绑包可从jaxb.java.net获取。下载并安装JAXBRI捆绑包。示例......
  • Java 中文官方教程 2022 版(三十四)
    原文:docs.oracle.com/javase/tutorial/reallybigindex.html长期持久性原文:docs.oracle.com/javase/tutorial/javabeans/advanced/longpersistence.html长期持久性是一种模型,可以将bean保存为XML格式。有关XML格式和如何为非bean实现长期持久性的信息,请参阅XML模......
  • Java 中文官方教程 2022 版(三十六)
    原文:docs.oracle.com/javase/tutorial/reallybigindex.html使用高级数据类型原文:docs.oracle.com/javase/tutorial/jdbc/basics/sqltypes.html本节介绍的高级数据类型使关系数据库在表列值方面更加灵活。例如,列可以用于存储BLOB(二进制大对象)值,可以以原始字节形式存储非常......
  • Java 中文官方教程 2022 版(三十七)
    原文:docs.oracle.com/javase/tutorial/reallybigindex.html使用GUIAPI的JDBC原文:docs.oracle.com/javase/tutorial/jdbc/basics/jdbcswing.html示例CoffeesFrame.java演示了如何将JDBC与GUIAPI集成,特别是SwingAPI。它在表中显示了COFFEES数据库表的内容,并包含字......
  • Java 中文官方教程 2022 版(二十)
    原文:docs.oracle.com/javase/tutorial/reallybigindex.html解决常见组件问题原文:docs.oracle.com/javase/tutorial/uiswing/components/problems.html本节讨论您在使用组件时可能遇到的问题。如果在本节中找不到您的问题,请参考以下章节:解决使用其他Swing功能的常见问......