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.hqwc.cn/news/416984.html

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

相关文章

积分梳状滤波器CIC原理与实现

CIC&#xff08;Cascade Intergrator Comb&#xff09;&#xff1a;级联积分梳状滤波器&#xff0c;是由积分器和梳状滤波器级联而得。滤波器系数为1&#xff0c;无需对系数进行存储&#xff0c;只有加法器、积分器和寄存器&#xff0c;资源消耗少&#xff0c;运算速率高&#…

MyBatis 使用报错: Can‘t generate mapping method with primitive return type

文章目录 前言问题原因解决方案个人简介 前言 今天在新项目中使用 MyBatis 报如下错误&#xff1a;Cant generate mapping method with primitive return type 问题原因 发现是 Mapper 注解引入错误&#xff0c;错误引入 org.mapstruct.Mapper, 实际应该引入 org.apache.ibat…

python实操之网络爬虫介绍

一、什么是网络爬虫 网络爬虫&#xff0c;也可以叫做网络数据采集更容易理解。它是指通过编程向网络服务器&#xff08;web&#xff09;请求数据&#xff08;HTML表单&#xff09;&#xff0c;然后解析HTML&#xff0c;提取出自己想要的数据。 它包括了根据url获取HTML数据、解…

6.4.4释放音频

6.4.4释放音频 许多Flash动画里的音乐或歌曲非常好听&#xff0c;能不能在没有源文件的情况下把里面的声音文件取出来呢&#xff1f;利用Swf2VideoConverter2可以轻松做到这一点。 1&#xff0e;单击“添加”按钮&#xff0c;在弹出的下拉菜单中选择“添加文件”&#xff0c;…

火速收藏!2024 新年微信红包封面领取全攻略

2024“龙”重登场&#xff01;今年有哪些令人期待的红包封面&#xff1f; 前方大批精美红包封面来袭&#xff0c;全新品牌氛围红包封面上线&#xff0c;支持品牌定制特色氛围元素&#xff0c;沉浸感受浓浓年味儿&#xff0c;收获满满惊喜&#xff01; 新年开好运&#xff0c;微…

1432 - 走出迷宫的最少步数-广搜

代码 #include<bits/stdc.h> using namespace std; char a[51][51]; int r,c; int fx[4]{0,0,1,-1}; int fy[4]{1,-1,0,0}; int tx,ty; struct Node{int x,y,step; }; int bfs(int x,int y){a[x][y]#;queue<Node> q;q.push({x,y,1});while(!q.empty()){Node Curre…

小白快速上手maven【基础篇】

Maven的概念和作用 Maven是什么&#xff1f;POM的概念 Maven的作用&#xff1f; 提供自动化构建项目的方式&#xff0c;并且统一了项目的结构管理项目中的依赖 Maven基础概念-仓库&#xff0c;坐标&#xff0c;仓库配置 仓库&#xff1a;用于存储各种jar包资源 根据功能的不…

git本地分支的合并

目录 第一章、本地分支的切换测试1.1&#xff09;切换之前的master分支下文件内容1.2&#xff09;切换到develop分支后修改文件1.3&#xff09;切回master分支出现报错&#xff1a;1.4&#xff09;报错分析 第二章、解决方式2.1&#xff09;方式1&#xff1a;commit2.2&#xf…

NFS 共享存储服务

一、存储和NFS共享 1.存储类型分为三种 直连式存储&#xff1a;Direct-Attached Storage&#xff0c;简称DAS 网络附加存储&#xff1a;Network-Attached Storage&#xff0c;简称NAS 存储区域网络&#xff1a;Storage Area Network&#xff0c;简称SAN DAS:存储和主机是直连…

【JupyterLab】在 conda 虚拟环境中 JupyterLab 的安装与使用

【JupyterLab】在 conda 虚拟环境中 JupyterLab 的安装与使用 1 JupyterLab 介绍2 安装2.1 Jupyter Kernel 与 conda 虚拟环境 3 使用3.1 安装中文语言包(Optional)3.2 启动3.3 常用快捷键3.3.1 命令模式下 3.4 远程访问个人计算机3.4.1 局域网下 1 JupyterLab 介绍 官方文档: …

PMIC 基础知识浅析(四)

PMIC 后端研究现状&#xff1a; 现今针对便携式移动平台的电源管理芯片仍以传统分离型 PMIC为主。 根据后端设计的特点&#xff0c;传统分离型 PMIC 又可分三大类。 控制芯片与开关 MOSFET 分离型&#xff0c;MOSFET 外置于PCB上&#xff0c;芯片仅提供智能控制功能。 此类IC…

20240116使用Firefly的AIO-3399J的预编译的Android10固件确认RT5640声卡信息

20240116使用Firefly的AIO-3399J的预编译的Android10固件确认RT5640声卡信息 2024/1/16 17:55 百度&#xff1a;RK3399 ALC5640 RK3399 RT5640 BING&#xff1a;RK3399 ALC5640 LINE-IN接麦克风不会有声音的。 耳机只有右边有声音&#xff0c;但是偏小&#xff0c;可以通过音量…