java8 列表通过 stream流 根据对象属性去重的三种实现方法

news2024/11/14 2:39:48

java8 列表通过 stream流 根据对象属性去重的三种实现方法

一、简单去重

public class DistinctTest {
    /**
     * 没有重写 equals 方法
     */
    @Setter
    @Getter
    @ToString
    @AllArgsConstructor
    @NoArgsConstructor
    public static class User {
        private String name;
        private Integer age;
    }

    /**
     * lombok(@Data) 重写了 equals 方法 和 hashCode 方法
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class User2 {
        private String name;
        private Integer age;
    }

    @Test
    public void easyTest() {
        List<Integer> integers = Arrays.asList(1, 1, 2, 3, 4, 4, 5, 6, 77, 77);
        System.out.println("======== 数字去重 =========");
        System.out.print("原数字列表:");
        integers.forEach(x -> System.out.print(x + " "));
        System.out.println();
        System.out.print("去重后数字列表:");
        integers.stream().distinct().collect(Collectors.toList()).forEach(x -> System.out.print(x + " "));

        System.out.println();
        System.out.println();

        List<User> list = Lists.newArrayList();
        User three = new User("张三", 18);
        User three2 = new User("张三", 18);
        User three3 = new User("张三", 24);
        User four = new User("李四", 18);
        list.add(three);
        list.add(three);
        list.add(three2);
        list.add(three3);
        list.add(four);
        System.out.println("======== 没有重写equals方法的话,只能对相同对象(如:three)进行去重,不能做到元素相同就可以去重) =========");
        // 没有重写 equals 方法时,使用的是超类 Object 的 equals 方法
        // 等价于两个对象 == 的比较,只能筛选同一个对象
        System.out.println("初始对象列表:");
        list.forEach(System.out::println);
        System.out.println("简单去重后初始对象列表:");
        list.stream().distinct().collect(Collectors.toList()).forEach(System.out::println);

        System.out.println();
        System.out.println();

        List<User2> list2 = Lists.newArrayList();
        User2 five = new User2("王五", 18);
        User2 five2 = new User2("王五", 18);
        User2 five3 = new User2("王五", 24);
        User2 two = new User2("二蛋", 18);
        list2.add(five);
        list2.add(five);
        list2.add(five2);
        list2.add(five3);
        list2.add(two);
        System.out.println("======== 重写了equals方法的话,可以做到元素相同就可以去重) =========");
        // 所以如果只需要写好 equals 方法 和 hashCode 方法 也能做到指定属性的去重
        System.out.println("初始对象列表:");
        list2.forEach(System.out::println);
        System.out.println("简单去重后初始对象列表:");
        list2.stream().distinct().collect(Collectors.toList()).forEach(System.out::println);
    }
}

二、根据对象某个属性去重

0、User对象

    /**
     * 没有重写 equals 方法
     */
    @Setter
    @Getter
    @ToString
    @AllArgsConstructor
    @NoArgsConstructor
    public static class User {
        private String name;
        private Integer age;
    }

1、使用filter进行去重

    @Test
    public void objectTest() {
        List<User> list = Arrays.asList(
                new User(null, 18),
                new User("张三", null),
                null,
                new User("张三", 24),
                new User("张三5", 24),
                new User("李四", 18)
        );
        System.out.println("初始对象列表:");
        list.forEach(System.out::println);
        System.out.println();
        System.out.println("======== 使用 filter ,根据特定属性进行过滤(重不重写equals方法都不重要) =========");
        System.out.println("根据名字过滤后的对象列表:");
        // 第一个 filter 是用于过滤 第二个 filter 是用于去重
        List<User> collect = list.stream().filter(o -> o != null && o.getName() != null)
                .filter(distinctPredicate(User::getName)).collect(Collectors.toList());
        collect.forEach(System.out::println);
        System.out.println("根据年龄过滤后的对象列表:");
        List<User> collect1 = list.stream().filter(o -> o != null && o.getAge() != null)
                .filter(distinctPredicate(User::getAge)).collect(Collectors.toList());
        collect1.forEach(System.out::println);
    }

    /**
     * 列表对象去重
     */
    public <K, T> Predicate<K> distinctPredicate(Function<K, T> function) {
        // 因为stream流是多线程操作所以需要使用线程安全的ConcurrentHashMap
        ConcurrentHashMap<T, Boolean> map = new ConcurrentHashMap<>();
        return t -> null == map.putIfAbsent(function.apply(t), true);
    }
测试

在这里插入图片描述

①、疑惑

既然 filter 里面调用的是 distinctPredicate 方法,而该方法每次都 new 一个新的 map 对象,那么 map 就是新的,怎么能做到可以过滤呢

②、解惑

先看一下 filter 的部分实现逻辑,他使用了函数式接口 Predicate ,每次调用filter时,会使用 predicate 对象的 test 方法,这个对象的test 方法就是 null == map.putIfAbsent(function.apply(t), true)

而 distinctPredicate 方法作用就是生成了一个线程安全的 Map 集合,和一个 predicate 对象,且该对象的 test 方法为 null == map.putIfAbsent(function.apply(t), true)

之后 stream 流的 filter 方法每次都只会使用 predicate 对象的 test 方法,而该 test 方法中的 map 对象在该流中是唯一的,并不会重新初始化

    @Override
    public final Stream<P_OUT> filter(Predicate<? super P_OUT> predicate) {
        Objects.requireNonNull(predicate);
        return new StatelessOp<P_OUT, P_OUT>(this, StreamShape.REFERENCE,
                                     StreamOpFlag.NOT_SIZED) {
            @Override
            Sink<P_OUT> opWrapSink(int flags, Sink<P_OUT> sink) {
                return new Sink.ChainedReference<P_OUT, P_OUT>(sink) {
                    @Override
                    public void begin(long size) {
                        downstream.begin(-1);
                    }

                    @Override
                    public void accept(P_OUT u) {
                        if (predicate.test(u))
                            downstream.accept(u);
                    }
                };
            }
        };
    }

2、使用Collectors.toMap() 实现根据某一属性去重(这个可以实现保留前一个还是后一个)

要注意 Collectors.toMap(key,value) 中 value 不能为空,会报错,key 可以为 null,但会被转换为字符串的 “null”

    @Test
    public void objectTest() {
        List<User> list = Arrays.asList(
                new User(null, 18),
                new User("张三", null),
                null,
                new User("张三", 24),
                new User("张三5", 24),
                new User("李四", 18)
        );

        System.out.println("初始对象列表:");
        list.forEach(System.out::println);
        System.out.println();
        System.out.println("======== 使用 Collectors.toMap() 实现根据某一属性去重 =========");
        System.out.println("根据名字过滤后的对象列表 写法1:");
        // (v1, v2) -> v1 的意思 两个名字一样的话(key一样),存前一个 value 值
        Map<String, User> collect = list.stream().filter(Objects::nonNull).collect(Collectors.toMap(User::getName, o -> o, (v1, v2) -> v1));
        // o -> o 也可以写为 Function.identity() ,两个是一样的,但后者可能比较优雅,但阅读性不高,如下
        // Map<String, User> collect = list.stream().filter(Objects::nonNull).collect(Collectors.toMap(User::getName, Function.identity(), (v1, v2) -> v1));
        List<User> list2 = new ArrayList<>(collect.values());
        list2.forEach(System.out::println);
        System.out.println("根据名字过滤后的对象列表 写法2:");
        Map<String, User> map2 = list.stream().filter(o -> o != null && o.getName() != null)
                .collect(HashMap::new, (m, o) -> m.put(o.getName(), o), HashMap::putAll);
        list2 = new ArrayList<>(map2.values());
        list2.forEach(System.out::println);
        
        System.out.println("根据年龄过滤后的对象列表:");
        // (v1, k2) -> v2 的意思 两个年龄一样的话(key一样),存后一个 value 值
        Map<Integer, User> collect2 = list.stream().filter(Objects::nonNull).collect(Collectors.toMap(User::getAge, o -> o, (v1, v2) -> v2));
        list2 = new ArrayList<>(collect2.values());
        list2.forEach(System.out::println);

    }
测试

在这里插入图片描述

2.2、Collectors.toMap() 的变种 使用 Collectors.collectingAndThen()

Collectors.collectingAndThen() 函数 它可接受两个参数,第一个参数用于 reduce操作,而第二参数用于 map操作。

也就是,先把流中的所有元素传递给第一个参数,然后把生成的集合传递给第二个参数来处理。


    @Test
    public void objectTest() {
        List<User> list = Arrays.asList(
                new User(null, 18),
                new User("张三", null),
                null,
                new User("张三", 24),
                new User("张三5", 24),
                new User("李四", 18)
        );
        System.out.println("初始对象列表:");
        list.forEach(System.out::println);
        System.out.println();
        System.out.println("======== 使用 Collectors.toMap() 实现根据某一属性去重 =========");
        System.out.println("根据名字过滤后的对象列表:");
        ArrayList<User> collect1 = list.stream().filter(o -> o != null && o.getName() != null).collect(
                Collectors.collectingAndThen(Collectors.toMap(User::getName, o -> o, (k1, k2) -> k2), x-> new ArrayList<>(x.values())));
        collect1.forEach(System.out::println);
        System.out.println("======== 或者 ==========");
        List<User> collect = list.stream().filter(o -> o != null && o.getName() != null).collect(
                Collectors.collectingAndThen(Collectors.toCollection(
                        () -> new TreeSet<>(Comparator.comparing(User::getName))), ArrayList<User>::new));
        collect.forEach(System.out::println);
    }
测试

在这里插入图片描述

三、测试哪个方法比较快

    @Test
    public void objectTest() {
        List<User> list = new ArrayList<>(Arrays.asList(
                new User(null, 18),
                new User("张三", null),
                null,
                new User("张三", 24),
                new User("张三5", 24),
                new User("李四", 18)
        ));
        for (int i = 0; i < 100000; i++) {
            list.add(new User((Math.random() * 10) + "", (int) (Math.random() * 10)));
        }
        System.out.println("======== 测试速度 =========");
        long startTime = System.currentTimeMillis();
        List<User> list1 = list.stream().filter(o -> o != null && o.getName() != null)
                .filter(distinctPredicate(User::getName)).collect(Collectors.toList());
        long endTime = System.currentTimeMillis();
        System.out.println("filter 用时 :" + (endTime - startTime));

        System.out.println();
        startTime = System.currentTimeMillis();
        Map<String, User> map1 = list.stream().filter(o -> o != null && o.getName() != null)
                .collect(Collectors.toMap(User::getName, o -> o, (v1, v2) -> v1));
        List<User> list2 = new ArrayList<>(map1.values());
        endTime = System.currentTimeMillis();
        System.out.println("map1 用时 :" + (endTime - startTime));

        System.out.println();
        startTime = System.currentTimeMillis();
        ArrayList<User> list3 = list.stream().filter(o -> o != null && o.getName() != null).collect(
                Collectors.collectingAndThen(Collectors.toMap(User::getName, o -> o, (k1, k2) -> k2), x -> new ArrayList<>(x.values())));
        endTime = System.currentTimeMillis();
        System.out.println("map2 用时 :" + (endTime - startTime));

        System.out.println();
        startTime = System.currentTimeMillis();
        List<User> list4 = list.stream().filter(o -> o != null && o.getName() != null).collect(
                Collectors.collectingAndThen(Collectors.toCollection(
                        () -> new TreeSet<>(Comparator.comparing(User::getName))), ArrayList<User>::new));
        endTime = System.currentTimeMillis();
        System.out.println("map3 用时 :" + (endTime - startTime));

        System.out.println();
        startTime = System.currentTimeMillis();
        Map<String, User> map2 = list.stream().filter(o -> o != null && o.getName() != null)
                .collect(HashMap::new, (m, o) -> m.put(o.getName(), o), HashMap::putAll);
        List<User> list5 = new ArrayList<>(map2.values());
        endTime = System.currentTimeMillis();
        System.out.println("map4 用时 :" + (endTime - startTime));
    }

测试:

在这里插入图片描述

四、结论

1、去重最快:

	ArrayList<User> list3 = list.stream().filter(o -> o != null && o.getName() != null).collect(
                Collectors.collectingAndThen(Collectors.toMap(User::getName, o -> o, (k1, k2) -> k2), x -> new ArrayList<>(x.values())));
	// 或者
	Map<String, User> map2 = list.stream().filter(o -> o != null && o.getName() != null)
                .collect(HashMap::new, (m, o) -> m.put(o.getName(), o), HashMap::putAll);
	List<User> list5 = new ArrayList<>(map2.values());

2、其次

        Map<String, User> map1 = list.stream().filter(o -> o != null && o.getName() != null)
                .collect(Collectors.toMap(User::getName, o -> o, (v1, v2) -> v1));
        List<User> list2 = new ArrayList<>(map1.values());

		// distinctPredicate 是一个方法 本文中有 ,可以 ctrl + f 查找
        List<User> list1 = list.stream().filter(o -> o != null && o.getName() != null)
                .filter(distinctPredicate(User::getName)).collect(Collectors.toList());

3、最慢

	List<User> list4 = list.stream().filter(o -> o != null && o.getName() != null).collect(
                Collectors.collectingAndThen(Collectors.toCollection(
                        () -> new TreeSet<>(Comparator.comparing(User::getName))), ArrayList<User>::new));

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

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

相关文章

梁山泊国潮风礼盒,传承经典,贺礼新春

在春节来临之际&#xff0c;梁山泊隆重推出新年中国红礼盒酒&#xff0c;为您传递新年的祝福与关爱。这款酒以其独特的魅力&#xff0c;为您带来美好的祝愿和愉悦的享受。中国风国潮礼盒采用中国传统红色为主色调&#xff0c;象征着吉祥、喜庆和繁荣。红色的背景上&#xff0c;…

探索curl的高级应用:HTTP请求的大师级技巧

探索curl的高级应用&#xff1a;HTTP请求的大师级技巧 引言高级用法概览1. HTTP请求与响应处理2. 身份验证与安全3. 进阶技巧4. Cookie管理与会话保持5. 脚本自动化 HTTP请求与响应处理1. 自定义请求头2. 发送数据3. 处理响应 身份验证与安全1. 基本认证2. 摘要认证3. HTTPS安全…

禅道使用之项目的过程管理

目录 一.禅道介绍 二.禅道下载 三. 禅道的使用 3.1.禅道管理员管理账号 3.2.禅道管理产品经理角色操作 3.3.禅道管理项目经理角色操作 3.4.禅道管理测试主管角色操作 3.5.禅道管理产品经理发布版本 好啦今天就这里了哦&#xff01;&#xff01;&#xff01;希望能帮到你…

Unity-Arduino Bluetooth Plugin蓝牙插件使用时需要注意的一些事项(附插件下载链接)

一些参考链接 1.Android 无法扫描蓝牙设备踩坑 2.权限相关 1-首先要明确你的蓝牙设备是经典蓝牙还是低功耗&#xff08;BLE)蓝牙&#xff1a; 转载&#xff1a;Android蓝牙开发—经典蓝牙和BLE&#xff08;低功耗&#xff09;蓝牙的区别 2.如果是BLE蓝牙&#xff0c;需要打勾…

Github 2024-01-20开源项目日报 Top10

根据Github Trendings的统计&#xff0c;今日(2024-01-20统计)共有10个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Python项目4Jupyter Notebook项目2Lua项目1C项目1PHP项目1Vue项目1非开发语言项目1C项目1 PhotoMaker: 定制逼…

nvm, node.js, npm, yarn 安装配置

文章目录 nvm 安装node.js 安装npm yarn 配置 nvm 安装 nvm 是一个 node.js 管理工具&#xff0c;可以快捷下载安装使用多个版本的node.js linux 命令行输入&#xff1a; curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bashwget -qO- https…

程序员的福利到了,轮转数组,经典算法实战

&#x1f3c6;作者简介&#xff0c;普修罗双战士&#xff0c;一直追求不断学习和成长&#xff0c;在技术的道路上持续探索和实践。 &#x1f3c6;多年互联网行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &#x1f389;欢迎 &#x1f44d;点赞✍评论…

ES框架详解

ES框架详解 1、全文检索的介绍 ​ 那么对于一般的公司&#xff0c;初期是没有那么多数据的&#xff0c;所以很多公司更倾向于使用传统的数据库&#xff1a;mysql&#xff1b;比如我们要查找关键字”传智播客“&#xff0c;那么查询的方式大概就是:select * from table where …

2024WebGIS新手必看学习攻略(2)

上期给大家分享了webgis开发学习的第一期&#xff0c;点这里&#xff1a;2024WebGIS新手必看学习攻略&#xff08;1&#xff09;https://mp.csdn.net/mp_blog/creation/editor/135680818 主要讲了webgis最基本的前置知识&#xff0c;上期我们介绍到学习webGIS的语言&#xff0…

OpenSource - 工具管理器easy-manager-tool

文章目录 功能说明运行配置环境配置启动docker部署 项目安全UI展示 Easy-Manager-Tool 打造软件行业首款集成工具&#xff0c;不管你是程序员&#xff0c;测试&#xff0c;运维等都可以使用该软件来提升自己的工作效率。 Easy-Manager-Tool 的诞生是为了解决软件行业众多参与者…

java发送邮件、接收邮件

邮件协议 SMTP SMTP (Simple Mail Transfer Protocol)&#xff0c;即简单邮件传输协议 默认端口是25&#xff0c;通过SSL协议加密之后的默认端口是465 用户必须首先设置 SMTP 服务器&#xff0c;然后才能配置电子邮件客户端与其连接。完成此操作后&#xff0c;用户按下电子…

YOLOv5改进 | 主干篇 | 华为移动端模型GhostnetV2一种移动端的专用特征提取网络

一、本文介绍 本文给大家带来的改进机制是华为移动端模型Ghostnetv2,华为GhostNetV2是为移动应用设计的轻量级卷积神经网络(CNN),旨在提供更快的推理速度,其引入了一种硬件友好的注意力机制,称为DFC注意力。这个注意力机制是基于全连接层构建的,它的设计目的是在通用硬…

【Qt】对象树与坐标系

需要云服务器等云产品来学习Linux的同学可以移步/-->腾讯云<--/-->阿里云<--/-->华为云<--/官网&#xff0c;轻量型云服务器低至112元/年&#xff0c;新用户首次下单享超低折扣。 目录 一、Qt Creator快捷键 二、对象树 1、对象树的析构 2、自定义类的编写…

K8S--安装Nginx

原文网址&#xff1a;K8S--安装Nginx-CSDN博客 简介 本文介绍K8S安装Nginx的方法。 1.创建Nginx目录及配置文件 mkdir -p /work/devops/k8s/app/nginx/{config,html} 在config目录下创建nginx.conf配置文件&#xff0c;内容如下&#xff1a; # events必须要有 events {wo…

TestCaseAssiant使用说明

目录 说明 工具界面 功能描述 Xmind转测试用例 测试组件 测试用例 用例优先级 用例前提 用例操作步骤 用例期望结果 Excel测试用例转Testlink xml 用例模板 使用技巧: TestLink Xml转Excel测试用例 说明 本文为小编之前博文中介绍的工具使用说明 Xmind转Excel测…

市场复盘总结 20240119

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整&#xff0c;采用龙空龙模式 昨日主题投资 连板进级率 11/39 28.2% 二进三&#xff1a; 进级率低 43% 最常用的二种方法&#xff1a; 方…

使用emby在Nas群晖搭建一个私人影院

1、安装Emby 打开套件中心搜索emby并安装 2、新增一个共享文件夹 设置好&#xff0c;无脑下一步到应用 给emby赋予这个文件夹的读写权限 保存 3、打开emby service 选择媒体库

SDL2 连续帧图像显示

QT使用SDL多窗口显示视频&#xff08;linux&#xff0c;ubuntu&#xff09;_linux qt sdl-CSDN博客 QT使用SDL播放YUV视频 - C - QT SDL调用OPENGL渲染图像 - C - 心得 C 使用SDL显示RGB图像数据_c sdl-CSDN博客 SDL库入门&#xff1a;掌握跨平台游戏开发和多媒体编程_sdl开…

SpringBoot 3.1.7 集成 SpringCloud OpenFeign

一、环境准备 准备一个项目&#xff0c;项目中有3个模块&#xff0c;1 1. FeignClient&#xff08;下图的goods-center&#xff09; 2. FeignApi(下图的user-api) &#xff0c;这个模块为其他2个模块的公共依赖模块&#xff0c;相当于2个项目通信的协议 3. FeignServer (下…

基于 UniAPP 社区论坛项目多端开发实战

社区论坛项目多端开发实战 基于 UniAPP 社区论坛项目多端开发实战一、项目准备1.1 ThinkSNS 简介及相关文档1.2 使用 UniAPP 构建项目1.3 构建项目文件结构1.4 配置页面 TabBar 导航1.5 使用 npm 引入 uView UI 插件库 二、首页功能实现2.1 首页 header 广告位轮播图功能实现2.…