Java stream流支持多字段排序

news2024/11/13 16:40:57

背景

对于排序而言,比较常见的场景是前端传递所需的排序字段名和排序方向,然后通过stream流或者数据库来实现排序.
为动态接收参数,继承Map来支持多字段传入.另外stream流原生的sorted写起来相对比较繁琐,通过compartor方法封装构建多字段排序的逻辑.具体就是通过反射拿到对应字段的值,然后利用Compartor的comparing和thenComparing完成多字段排序.

代码

具体代码和测试结果如下

package xyz.yq56;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import cn.hutool.core.util.ReflectUtil;
import lombok.Data;

/**
 * 封装Map接收前端排序参数,并提供compartor,方便构建stream流sorted所需的Comparator
 * <pre>
 *     示例:
 *     {
 *         "fieldName": "ascend",
 *         "fieldName2": "descend"
 *     }
 * </pre>
 *
 * <pre>
 *     1. key 是排序字段
 *     2. value 是排序方向,可选值 ascend/descend 或者 asc/desc
 *     3. 排序字段属性需符合java的get规范,其中使用了反射获取field对应的get方法
 *     4. 排序字段的类型必须实现Comparable接口,否则抛出异常The field xx does not implement Comparable interface
 *     5. 注意这里用的是LinkedHashMap,保证排序参数的顺序.如果是HashMap,排序参数的顺序可能会丢失,甚至反过来
 *     6. 依赖了hutool的ReflectUtil,后续尽量移除此依赖
 * </pre>
 *
 * @author yi qiang
 * @date 2024-07-23 16:28:52
 */
@SuppressWarnings("all")
public class SortMap extends LinkedHashMap<String, String> {

    public Comparator compartor() {
        if (this.isEmpty()) {
            return null;
        }
        Comparator comparator = null;
        // 遍历排序参数,构建comparator
        // 可以用reduce,但是感觉还没三元表达式清晰
        for (Map.Entry<String, String> entry : this.entrySet()) {
            comparator = (comparator == null) ?
                         getTempComparator(entry.getKey(), entry.getValue()) :
                         //多字段排序,第二个字段开始就要使用thenComparing
                         comparator.thenComparing(getTempComparator(entry.getKey(), entry.getValue()));
        }
        return comparator;
    }

    private Comparator getTempComparator(String fieldName, String direction) {
        if ("descend".equalsIgnoreCase(direction) || "desc".equalsIgnoreCase(direction)) {
            return Comparator.comparing(getKeyExtractor(fieldName), Comparator.reverseOrder());
        }
        return Comparator.comparing(getKeyExtractor(fieldName));
    }

    /**
     * 排序属性值的映射函数
     */
    private Function getKeyExtractor(String fieldName) {
        return ele -> {
            Object o = ReflectUtil.getFieldValue(ele, fieldName);
            if (o instanceof Comparable) {
                return (Comparable) o;
            }
            throw new RuntimeException("The field " + fieldName + " does not implement Comparable interface");
        };
    }
    // 暂时未经过严格测试,比如父类属性,先不处理
    //    private static Object getFieldValue(Object ele,String fieldName) {
    //        try {
    //            Field declaredField = ele.getClass().getDeclaredField(fieldName);
    //            declaredField.setAccessible(true);
    //            return declaredField.get(ele);
    //        } catch (Exception ex) {
    //            return null;
    //        }
    //    }

    public static void main(String[] args) {
        List<Test> tests = new ArrayList<>();
        Test test = new Test();
        test.setL(1);
        test.setDate(new Date());
        test.setS("c");
        tests.add(test);

        Test test1 = new Test();
        test1.setL(2);
        test1.setDate(new Date(System.currentTimeMillis() - 1000 * 60 * 60 * 24));
        test1.setS("a");

        tests.add(test1);

        Test test2 = new Test();
        test2.setL(2);
        test2.setDate(new Date(System.currentTimeMillis() - 1000 * 60 * 60 * 24));
        test2.setS("b");
        test2.setTest2(new Test2());
        tests.add(test2);

        SortMap sortMap = new SortMap();
        sortMap.put("l", "descend");
        sortMap.put("s", "descend");
        //测试Comparable接口未实现
        //sortMap.put("test2", "ascend");

        tests.stream().sorted(sortMap.compartor()).forEach(System.out::println);
    }

    @Data
    public static class Test {
        private long l;
        private String s;
        private Date date;
        private Test2 test2;
    }

    @Data
    public static class Test2 {
        private long l;
        private String s;
    }
}

运行结果
在这里插入图片描述
如果此工具能帮到大家,请动动手点个赞.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1990506.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

怎么在 React Native 应用中处理深度链接?

深度链接是一种技术&#xff0c;其中给定的 URL 或资源用于在移动设备上打开特定页面或屏幕。因此&#xff0c;深度链接可以引导用户到应用程序内的特定屏幕&#xff0c;而不仅仅是启动移动设备上的应用程序&#xff0c;从而提供更好的用户体验。这个特定的屏幕可能位于一系列层…

docker 好用的加速器

cd /etc/docker vi daemon.json { "registry-mirrors":["https://docker.rainbond.cc"] }

SpringIOC整合dbUtil做的增删改查以及转账业务的实现

目录 一、xml方式实现 1.介绍lombok插件 2.功能 3.步骤 3.1 idea安装插件(只做一次) 3.2 添加坐标 3.3 编写注解 4.核心类 4.1 QueryRunner 4.2 query() 查询 4.3 update() 增删改 5.配置文件applicationContext.xml 6.junit测试 6.1使用步骤 6.1.1 坐标 6.1.2…

【Material-UI】Button Group 中的 Disabled Elevation 功能

文章目录 一、Button Group 组件概述二、什么是 Elevation&#xff1f;三、为什么需要禁用 Elevation&#xff1f;四、使用 disableElevation 属性五、属性解析1. disableElevation 属性2. variant 属性3. aria-label 属性 六、应用场景1. 表单操作2. 工具栏3. 导航按钮 七、样…

vue中v-html 后端返回html + script js中click事件不生效

效果图&#xff1a; 需求&#xff1a;点击加号执行后端返回的script中的代码 后端返回的html&#xff1a; <!DOCTYPE html> <html langzh> <head> <title>xxx</title> <style>body{font-size: 14px}p{text-indent: 30px;}textarea{width…

PythonStudio 控件使用常用方式(十三)TScrollBox

PythonStudio是一个极强的开发Python的IDE工具&#xff0c;它使用的是Delphi的控件&#xff0c;常用的内容是与Delphi一致的。但是相关文档并一定完整。现在我试试能否逐步把它的控件常用用法写一点点&#xff0c;也作为PythonStudio的参考。 从1.2.1版开始&#xff0c;Python…

(Qt) QThread 信号槽所在线程

文章目录 &#x1f481;&#x1f3fb;前言&#x1f481;&#x1f3fb;Code&#x1f481;&#x1f3fb;‍♂️Code&#x1f481;&#x1f3fb;‍♂️环境 &#x1f481;&#x1f3fb;当前线程信号&#x1f481;&#x1f3fb;‍♂️默认效果&#x1f481;&#x1f3fb;‍♂️Qt::…

RTOS(7)队列

1.队列的理论知识 下面的结构体里包含了&#xff1a;头部指针&#xff0c;写指针&#xff0c;读指针&#xff0c;长度&#xff0c;项目大小&#xff0c;两个链表&#xff1b; 写队列的时候&#xff0c;写指针指向头部&#xff0c;写进去之后&#xff0c;itemsize&#xff0c;移…

MySQL中的日志

错误日志 错误日志是MySQL中最重要的日志之一默认是开启的&#xff0c;它记录了MySQL启动和停止时&#xff0c;以及入伍再运行过程中发发生任何严重错误时的相关信息&#xff0c;当数据库出现任何故障无法正常运行时可以查看此日志。 二进制日志 二进制日志记录了所有的DDL语…

http跨域网络请求中的CORS(跨源资源共享) 那些事 -- HTTP跨域请求, chrome插件跨域请求使用详解, origin格式,origin通配符等

在我们进行网络应用开发的时候&#xff0c;如果用到了跨域网络请求&#xff0c;则不可避免的就会遇到http跨域网络请求 CORS的问题&#xff0c;今天就和大家来聊聊跨域网络请求中的CORS的那些事。 跨源资源共享&#xff08;CORS&#xff09; CORS 是一种基于 HTTP 头的机制&a…

ISO26262-MBD-静态验证在V左的布局考量

一、ISO26262-MBD-静态验证的迷惑 模型的开发方法&#xff08;Model-Based Design&#xff0c;MBD&#xff09;在汽车行业嵌入式软件开发中扮演着重要的角色&#xff0c;功能安全ISO26262要求对我们搭建的模型进行规范检查。合规检查我们可以借助第三方工具来实现静态检查&…

基于Django的图书管理系统【万能模板框架可调整增加】

文章目录 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主框架介绍项目框架每文一语 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主 框架介绍 Django是一个用于快速开发Web应用程序的高级Python开源框架。它遵循MVC…

去除富文本框的边框

<style lang"scss" scoped>::v-deep .textareaDeep .el-textarea__inner {border: none !important;box-shadow: none !important;padding: 0px; }</style> //添加类名 <el-inputclass"textareaDeep"type"textarea":rows"…

Go语言实现依赖注入

文章目录 前言依赖注入是什么依赖注入的好处是什么结构图应用程序上下文接口上下文管理器暴露的功能使用示例最后 前言 你好&#xff0c;我是醉墨居士&#xff0c;欢迎来到我的博客&#xff0c;今天带领大伙使用Go语言实现依赖自动注入&#xff0c;我们不会使用其它的第三方库…

域控安全:多种方式提取ntds.dit

ntdsutils.exe提取ntds.dit vssadmin提取ntds.dit vssown提取ntds.dit IFM ntds.dit: ntds.dit为AD的数据库&#xff0c;内容有域用户、域组、用户hash等信息&#xff0c;域控上的ntds.dit只有可以登录到域控的用户&#xff08;如域管用户、DC本地管理员用户&#xff09;可以…

HexView 刷写文件脚本处理工具-基本功能介绍(一)-基本界面

HexView主要可以显示不同文件格式的内容&#xff0c;包括Intel-HEX、Motorola S-record二进制文件或其他特定汽车制造商的文件格式。此外&#xff0c;它还可以执行多种数据处理操作&#xff0c;如校验和计算、签名生成、数据加密/解密或压缩/解压缩&#xff0c;甚至重新排列文件…

ubuntu创建txt

点击模版 右键 输入下面代码 sudo gedit txt文档.txt 然后就可以右键新建文本文件了 解开权限 sudo chmod -R 777 /home/sjxy/CQ

探索下一代互联网协议:IPv6的前景与优势

探索下一代互联网协议&#xff1a;IPv6的前景与优势 文章目录 探索下一代互联网协议&#xff1a;IPv6的前景与优势**IPv6 的特点****IPv6的基本首部****IPv6的地址****总结** 互联网的核心协议&#xff1a;从IPv4到IPv6 互联网的核心协议IP&#xff08;Internet Protocol&#…

【Nacos无压力源码领读】(一) Nacos 服务注册与订阅原理

本文将详细介绍 Nacos 客户端在启动时进行自动注册原理, 以及Nacos服务器是如何处理客户端的注册与订阅请求的; 本文会附带源码解读, 但不会死抠每一行代码, 主要是梳理整个流程, 过程中的关键步骤, 都会由思维导图的形式展现出来; 如果在阅读过程中对文中提到的 SpringBoot …

Comsol 弧形声学换能器声聚焦仿真

弧形声学换能器声聚焦是指将声波能量集中在弧形声学换能器的特定区域内&#xff0c;以实现更强的声场强度和分辨率。声聚焦在许多应用中非常有用&#xff0c;包括医学超声成像、声纳、声波聚焦破碎等领域。 弧形声学换能器的设计和优化可以通过以下几个因素来实现声聚焦&#…