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

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

一、简单去重

public class DistinctTest {/*** 没有重写 equals 方法*/@Setter@Getter@ToString@AllArgsConstructor@NoArgsConstructorpublic static class User {private String name;private Integer age;}/*** lombok(@Data) 重写了 equals 方法 和 hashCode 方法*/@Data@AllArgsConstructor@NoArgsConstructorpublic static class User2 {private String name;private Integer age;}@Testpublic 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@NoArgsConstructorpublic static class User {private String name;private Integer age;}

1、使用filter进行去重

    @Testpublic 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流是多线程操作所以需要使用线程安全的ConcurrentHashMapConcurrentHashMap<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 对象在该流中是唯一的,并不会重新初始化

    @Overridepublic 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) {@OverrideSink<P_OUT> opWrapSink(int flags, Sink<P_OUT> sink) {return new Sink.ChainedReference<P_OUT, P_OUT>(sink) {@Overridepublic void begin(long size) {downstream.begin(-1);}@Overridepublic void accept(P_OUT u) {if (predicate.test(u))downstream.accept(u);}};}};}

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

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

    @Testpublic 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操作。

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

@Testpublic 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);}
测试

在这里插入图片描述

三、测试哪个方法比较快

    @Testpublic 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.rhkb.cn/news/241696.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode.2788. 按分隔符拆分字符串

题目 题目链接 分析 题目的意思是给我们一个字符串数组和一个分隔符&#xff0c;让我们按照分隔符把字符串数组分割成新的字符串数组。 看到这个描述&#xff0c;这不就是直接就是利用 按照分隔符分割字符串的系统库函数split()&#xff0c;这个函数的意思就是 把一个字符串…

JDBC编程详细教程与示例源码

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl JDBC概述 为了在Java语言中提供对数据库访问的支持&#xff0c;Sun公司于1996年提供了一套访问数据库的标准Java类库JDBC。JDBC的全称是Java数据库连接(Java Database Conn…

使用条件操作执行控制图

Condition Action Behavior 此示例显示了具有多个分段的转换路径中的简单条件操作的行为。该图表使用传出转换的隐式排序 起初&#xff0c;chart处于休眠状态。状态A处于活动状态。条件C_one和C_two为false。事件E_one发生并唤醒chart&#xff0c;该图表通过层次结构从根向下…

第5章 运算符重载

运算符概述 纯单目运算符&#xff0c;只能有一个操作数&#xff0c;包括&#xff1a;!、~、sizeof、new、delete 等 纯双目运算符&#xff0c;只能有两个操作数&#xff0c;包括&#xff1a;[]、->、% 、 等 三目运算符&#xff0c;有三个操作数&#xff0c;如“ ? : ” 既…

C#,字符串匹配(模式搜索)有限自动机(Finite Automata)算法的源代码

一、有限状态自动机 图中两个圆圈&#xff0c;也叫节点&#xff0c;用于表示状态&#xff0c;从图中可以看成&#xff0c;它有两个状态&#xff0c;分别叫0和1。从每个节点出发&#xff0c;都会有若干条边。当处于某个状态时&#xff0c;如果输入的字符跟该节点出发的某条边的内…

go语言(八)---- map

map的声明方式有以下三种。 package mainimport "fmt"func main() {//第一种声明方式//声明map1是一个map类型&#xff0c;key是String&#xff0c;value是Stringvar myMap1 map[string] stringif myMap1 nil {fmt.Println("myMap1 是一个空map")}//在使…

idea中使用git提交代码报 Nothing To commit No changes detected

问题描述 在idea中右键&#xff0c;开始将变更的代码进行提交的时候&#xff0c;【Commit Directory】点击提交的时候 报 Nothing To commit No changes detected解决方案 在这里点击Test 看看是不是能下面显示git版本&#xff0c;不行的话 会显示一个 fix的字样&#xff0c;行…

阿里云ECS使用docker搭建mysql服务

目录 1.确保正确安装好docker 2.安装mysql镜像 3.创建容器&#xff08;设置端口映射、目录映射&#xff09; 1.确保正确安装好docker 安装教程&#xff1a; 阿里云ECS(CentOS镜像)安装docker-CSDN博客https://blog.csdn.net/qq_62262918/article/details/135686614?spm10…

Android平台Unity下如何通过WebCamTexture采集摄像头数据并推送至RTMP服务器或轻量级RTSP服务

技术背景 我们在对接Unity下推送模块的时候&#xff0c;遇到这样的技术诉求&#xff0c;开发者希望在Android的Unity场景下&#xff0c;获取到前后摄像头的数据&#xff0c;并投递到RTMP服务器&#xff0c;实现低延迟的数据采集处理。 在此之前&#xff0c;我们已经有了非常成…

如何下载OpenStreetMap(OSM)最新数据

OpenStreetMap&#xff08;OSM&#xff09;是一个开源的地图项目&#xff0c;旨在创建和提供免费、可自由使用、可编辑的地图数据和地图服务。以下是关于OpenStreetMap的一些关键信息&#xff1a; 社区驱动&#xff1a; OpenStreetMap是由一个全球性的志愿者社区共同创建和维护…

新上线一个IT公司微信小程序

项目介绍 项目背景: 一家IT公司,业务包含以下六大块: 1、IT设备回收 2、IT设备租赁 3、IT设备销售 4、IT设备维修 5、IT外包 6、IT软件开发 通过小程序,提供在线下单,在线制单,在线销售,业务介绍,推广,会员 项目目的: 业务介绍: 包含企业业务介绍 客户需…

怎么样的布局是符合可制造性的PCB布局?

满足可制造性、可装配性、可维修性要求&#xff0c;方便调试的时候于检测和返修&#xff0c;能够方便的拆卸器件&#xff1a; 1&#xff09;极性器件的方向不要超过2种&#xff0c;最好都进行统一方向等要求&#xff0c;如图1-1所示&#xff1b; 图1-1 极性器件方向统一摆放 2…

【Qt5】QString的成员函数trimmed

2024年1月19日&#xff0c;周五下午 QString 的 trimmed 方法是用于移除字符串两端的空白字符&#xff08;空格、制表符、换行符等&#xff09;的方法。它返回一个新的字符串&#xff0c;该字符串是原始字符串去除两端空白后的结果。 下面是一个简单的示例&#xff1a; #incl…

轻量化/高效扩散模型文献综述

&#x1f380;个人主页&#xff1a; https://zhangxiaoshu.blog.csdn.net &#x1f4e2;欢迎大家&#xff1a;关注&#x1f50d;点赞&#x1f44d;评论&#x1f4dd;收藏⭐️&#xff0c;如有错误敬请指正! &#x1f495;未来很长&#xff0c;值得我们全力奔赴更美好的生活&…

Unity—配置lua环境变量+VSCode 搭建 Lua 开发环境

每日一句&#xff1a;保持须臾的浪漫&#xff0c;理想的喧嚣&#xff0c;平等的热情 Windows 11下配置lua环境变量 一、lua-5.4.4版本安装到本地电脑 链接&#xff1a;https://pan.baidu.com/s/14pAlOjhzz2_jmvpRZf9u6Q?pwdhd4s 提取码&#xff1a;hd4s 二、高级系统设置 此电…

uniapp微信小程序投票系统实战 (SpringBoot2+vue3.2+element plus ) -投票帖子排行实现

锋哥原创的uniapp微信小程序投票系统实战&#xff1a; uniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )_哔哩哔哩_bilibiliuniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )共计21条视频…

如何实现 H5 秒开?

我在简历上写了精通 H5&#xff0c;结果面试官上来就问&#xff1a; 同学&#xff0c;你说你精通 H5 &#xff0c;那你能不能说一下怎么实现 H5 秒 由于没怎么做过性能优化&#xff0c;我只能凭着印象&#xff0c;断断续续地罗列了几点&#xff1a; 网络优化&#xff1a;http2、…

如何在CentOS 7 中基于OpenSSL 1.0 搭建Python 3.0 环境

1、下载 通过https://www.python.org/ftp/python/下载Python安装包&#xff0c;这里下载Python-3.10.9.tgz&#xff1b; 2、上传 借助MobaXterm等工具将Python安装包上传至/opt目录&#xff1b; 3、解压 将JDK压缩文件解压至/opt目录&#xff1a;tar -xvf /opt/Python-3.1…

基于Django的Python应用—学习笔记—功能完善

一、让用户可以输入信息 创建forms.py 创建基于表单的页面的方法几乎与前面创建网页一样&#xff1a;定义一个 URL &#xff0c;编写一个视图函数并编写一个模板。一个主要差别是&#xff0c;需要导入包含表单 的模块forms.py 。 from django import forms from .models impor…

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

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