JAVASE进阶:一文精通Stream流+函数式编程

news2025/7/18 13:56:03

👨‍🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
🌌上期文章:JAVASE进阶:源码精读——HashMap源码详细解析
📚订阅专栏:JAVASE进阶
希望文章对你们有所帮助

Stream流的使用是一种高级的写法,配合函数式编程(lambda表达式),能够极大简化我们程序的编写,有些二十行的代码也可以一行代码就实现,代码看起来也高雅了很多,这也是成为高级程序员的必会技能。

先讲原理,再实现一些例子。

一文精通Stream流+函数式编程

  • 引入
  • Stream流思想
  • 获取Stream流
  • Stream流的中间方法
  • Stream流终结方法详解
  • 收集方法collect
  • 练习
    • 数字过滤
    • 字符串过滤并收集
    • 自定义对象过滤并收集

引入

现在实现一个简单的需求:创建一个集合来存储名字,并且输出名字长度为3,且姓为“张”的所有名字。
这里直接用ArrayList,模拟起来还是很简单的,但是当相对还是要写很长的语句,而Stream流和Lambda表达式的集合能够极大地非常简化我们的代码编写,处理集合并输出的语句只需要一句话:

list.stream().filter(name->name.startWith("张")).filter(name->name.length()==3).forEach(name->System.out.println(name));

这代码对一个程序员的诱惑程度别提有多大。

Stream流思想

流:可以视为流水线,学过计算机系统都知道流水线的相关原理,它可以大大提升执行的性能。
如上例子可以分为流水线中的3个子任务:过滤留下“张”姓开头的;过滤留下长度为3的;输出。
对于每一个子任务,Stream流一般都会结合lambda表达式来简化集合、数组的操作。

Stream流的使用步骤:

1、先得到一条Stream流(流水线),并把数据放上去
2、利用Stream流的API进行各种操作:
(1)中间方法:过滤、转换
(2)终结方法:统计、打印

获取Stream流

获取方式方法名说明
单列集合stream()Collection中的默认方法
双列集合无法直接使用是Stream流,需要先通过keySet或entrySet转换成单列集合
数组stream(T[] array)Arrays工具类中的静态方法
一堆零散数据of(T…values)Stream接口中的静态方法

1、单列集合

ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "a", "b", "c", "d");
//Stream<String> stream = list.stream()可以获取流,但一般不这么用,而是直接一路链式编程
list.stream().forEach(s->System.out.println(s));//forEach是终结方法

2、双列集合

HashMap<String, Integer> map = new HashMap<>();
map.put("aaa", 111);map.put("bbb", 222);
//方法一,把key都拿出来单独做处理
map.keySet().stream().forEach(s->System.out.println(s));
//方法二,获取每个键值对对象
map.entrySet().stream().forEach(s->System,out.println(s));

3、数组

int[] arr = {1,2,3,4,5};
Arrays.stream(arr).forEach(s->System,out.println(s));

4、零散数据

Stream.of(1,2,3,4,5).forEach(s->System,out.println(s));
Stream.of("a","b","c").forEach(s->System,out.println(s));

在很多时候,都是推荐使用of方法,这是因为of方法形参写法为T...values,这是一种可变参数的书写形式,所以无论传递零散的数据还是一个数组,都是可以成功处理的。

但是!有一个很关键的点,of方法传入的数组里面的数据必须是引用数据类型的,如果是基本数据类型,那么只会把这一整个数组当作一个元素,输出的会是地址!

Stream流的中间方法

名称说明
filter过滤
limit获取前几个元素
skip跳过前几个元素
distinct元素去重,依赖hashCode和equals方法
concat合并a和b两个流为一个流
map转换流中的数据类型

注意:

1、中间方法会返回新的流,原来的Stream流只能使用一次,建议使用链式编程
2、修改Stream流中的数据,不会影响原来集合或数组中的数据

1、filter:
(1)匿名内部类

list.stream().filter(new Predicate<String>() {
	@Override
	public boolean test(String s){
		//返回值为true则表示当前数据留下
		return s.startWith("张");
	}
}).forEach(s->System.out.println(s));

(2)lambda表达式:

list.stream().filter(s->s.startWith("张")).forEach(s->System.out.println(s));

2、limit:

//输出前3个
list.stream().limit(3).forEach(s->System.out.println(s));

3、skip:

//跳过前4个
list.stream().skip(4).forEach(s->System.out.println(s));

4、distinct:

list.stream().distinct().forEach(s->System.out.println(s));

distinct的底层是非常复杂的,其核心是使用HashSet实现去重的

5、concat尽可能让两个stream流中的数据保持一致,不然会将数据类型变成它们的共同父类,导致缺失一些子类的特有功能:

Stream.concat(list1.stream(), list2.stream()).forEach(s->System.out.println(s));

6、map实现流中数据的类型转换,将String类型字符串中的年龄转换成int并输出:

Collections.addAll(list, "一二-12", "布布-14");

(1)匿名内部类:

list.stream().map(new Function<String, Integer>(){
	//String表示stream中的数据类型,Integer表示要转换的数据类型,不能写int,必须写包装类,默认为Object
	@Override
	public Integer apply(String s){
		//s表示stream流里的每一个数据,返回值为转换后的数据
		String[] arr = s.split("-");
		String ageString = arr[1];
		int age = Integer.parseInt(ageString);
		return age;
	}
}).forEach(s->System.out.println(s));

(2)lambda表达式:

list.stream().map(s->Integer.parseInt(s.split("-")[1]))
			 .forEach(s->System.out.println(s));

这个lambda表达式写起来剪枝不要太爽!

Stream流终结方法详解

名称说明
forEach遍历
count统计
toArray收集流中数据,放到数组中
collect收集流中数据,放到集合中

1、forEach:
(1)匿名内部类

list.stream().forEach(new Consumer<String>(){
	@Override
	public void accept(String s){
		System.out.println(s)
	}
});

(2)lambda表达式

list.stream().forEach(s->System.out.println(s));

2、count统计:

long count = list.stream().count();

3、toArray收集int类型数据到String类型数组中
(1)匿名内部类

//注意IntFunction中要传递数组泛型,所以需要传入"String[]"
String[] arr = list.stream().toArray(new new IntFunction<String[]>() {
	@Override
	public void apply(int value){
		//value表示数组的长度,方法体只需要创建一个数组即可,返回的类型要和泛型一致
		return new String[value];
	}
});
System.out.println(Arrays.toString(arr));

(2)lambda表达式

String[] arr = list.stream().toArray(value->new String[value]);
System.out.println(Arrays.toString(arr));

collect方法算是比较重要的方法了,在后面详细讲解。

收集方法collect

collect可以将流里面的数据收集到单列集合或双列集合中去的。

收集到List集合:

	List<String> list = new ArrayList<>();
    Collections.addAll(list, "张无忌-男-15", "小龙女-女-12", "一二-女-4", "布布-男-5");
    //把所有的男性收集起来
    List<String> newList = list.stream().filter(s -> "男".equals(s.split("-")[1])).collect(Collectors.toList());
    System.out.println(newList);

同样的,也可以收集到Set中去,单列集合还是比较方便的。

如果要收集到双列集合Map中,就需要指定键和值是哪个字段:

可以打开ToMap的底层源码,可以清楚的看到,调用toMap方法需要指定键和值的规则,其底层会自动的创建一个HashMap对象:
在这里插入图片描述
收集到Map集合里面的代码和细节如下所示,需要注意的是,要想收集Map必须保证键是不重复的。

(1)匿名内部类:

	//键:姓名 值:年龄
	Map<String, Integer> map = list.stream().filter(s -> "男".equals(s.split("-")[1]))
            /**
             * toMap:参数一表示键的生成规则
             *       参数二表示值的生成规则
             *
             *  参数一:
             *      Function泛型一:流中每一个数据的类型
             *              泛型二:Map集合中键的数据类型
             *      方法apply形参:依次表示流里面的每一个数据
             *              方法体:生成键的代码
             *              返回值:已经生成的键
             *  也就是说,Function中第一个泛型与apply的形参一致,第二个泛型与apply的返回类型一致
             *
             *  参数二同理
             */
            .collect(Collectors.toMap(
                    new Function<String, String>() {
                        @Override
                        public String apply(String s) {
                            return s.split("-")[0];
                        }
                    },
                    new Function<String, Integer>() {
                        @Override
                        public Integer apply(String s) {
                            return Integer.parseInt(s.split("-")[2]);
                        }
                    }
            ));
    System.out.println(map);

(2)lambda表达式

	Map<String, Integer> map = list.stream().filter(s -> "男".equals(s.split("-")[1]))
                .collect(Collectors.toMap(
                        s->s.split("-")[0],
                        s->Integer.parseInt(s.split("-")[2])));

练习

数字过滤

定义一个集合并添加整数1-10,过滤奇数只留下偶数,并将结果保存起来:

ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> list1 = list.stream().filter(s -> (s & 1) == 0).collect(Collectors.toList());
System.out.println(list1);

字符串过滤并收集

创建一个ArrayList集合,并添加以下字符串:

“zhangsan,23”
“lisi,24”
“wangwu,25”

保留年龄大于等于24岁的人,并将结果收集到Map集合中,姓名为键,年龄为值:

	public static void main(String[] args) {
        String s1 = "a", s2 = "b";
        //System.out.println(s1.compareTo(s2));
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "zhangsan,23", "lisi,24", "wangwu,25");
        //filter也可以转换成Integer类型再去做比较
        Map<String, Integer> map = list.stream().filter(s -> "24".compareTo(s.split(",")[1]) <= 0)
                .collect(Collectors.toMap(
                        s -> s.split(",")[0],
                        s -> Integer.parseInt(s.split(",")[1])
                ));
        System.out.println(map);
    }

自定义对象过滤并收集

在这里插入图片描述
自行去创建一个Actor对象,String类型的name、int类型的age,并且重写toString方法。

代码如下:

	public static void main(String[] args) {
        ArrayList<String> manList = new ArrayList<>();
        ArrayList<String> womenList = new ArrayList<>();
        Collections.addAll(manList, "坤坤,24", "再多,23", "看一眼,20", "就快,19", "要爆炸,22", "铁山靠,26");
        Collections.addAll(womenList, "朵拉,21", "杨幂,30", "杨超越,22", "露娜,25", "妲己,24", "安琪拉,23");
        Stream<String> stream1 = manList.stream().filter(s -> s.split(",")[0].length() == 3).limit(2);
        Stream<String> stream2 = womenList.stream().filter(s->s.startsWith("杨")).skip(1);
        List<Actor> list = Stream.concat(stream1, stream2).map(new Function<String, Actor>() {
            @Override
            public Actor apply(String s) {
                return new Actor(s.split(",")[0], Integer.parseInt(s.split(",")[1]));
            }
        }).collect(Collectors.toList());
    }
    System.out.println(list);

其中,map里面的匿名内部类还可以转化为lambda表达式:

s->new Actor(s.split(",")[0], Integer.parseInt(s.split(",")[1]))

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

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

相关文章

Prometheus服务器、Prometheus被监控端、Grafana、监控MySQL数据库、自动发现概述、配置自动发现、Alertmanager

目录 Prometheus概述 部署Prometheus服务器 环境说明&#xff1a; 配置时间 安装Prometheus服务器 添加被监控端 部署通用的监控exporter Grafana 概述 部署Grafana 展示node1的监控信息 监控MySQL数据库 配置MySQL 配置mysql exporter 配置mysql exporter 配置…

Linux network namespace 访问外网以及多命名空间通信(经典容器组网 veth pair + bridge 模式认知)

写在前面 整理K8s网络相关笔记博文内容涉及 Linux network namespace 访问外网方案 Demo实际上也就是 经典容器组网 veth pair bridge 模式理解不足小伙伴帮忙指正 不必太纠结于当下&#xff0c;也不必太忧虑未来&#xff0c;当你经历过一些事情的时候&#xff0c;眼前的风景已…

如何写好一个简历

如何编写求职简历 论Java程序员求职中简历的重要性 好简历的作用 在求职过程中&#xff0c;一份好的简历是非常重要的&#xff0c;它甚至可以直接决定能否被面试官认可。一份出色或者说是成功的个人简历&#xff0c;最根本的作用是能让看这份简历的人产生一定要见你的强烈愿…

面试经典150题——长度最小的子数组

​"In the midst of winter, I found there was, within me, an invincible summer." - Albert Camus 1. 题目描述 2. 题目分析与解析 首先理解题意&#xff0c;题目要求我们找到一个长度最小的 连续子数组 满足他们的和大于target&#xff0c;需要返回的是子数组的…

网安常用的三个攻击方式

1.渗透测试执行标准&#xff08;PTES&#xff09; 渗透测试执行标准由7个部分组成&#xff0c;包括前期交互、情报收集、威胁建模、漏洞分析、渗透利用、后渗透、撰写报告。在中国&#xff0c;渗透测试必须经过授权&#xff0c;否则就违背了网络安全法。前期交互主要指开展渗透…

Codeforces Round 113 (Div. 2)E. Tetrahedron(dp、递推)

文章目录 题面链接题意题解代码总结 题面 链接 E. Tetrahedron 题意 从一个顶点出发走过路径长度为n回到出发点的方案总数 题解 考虑dp f [ i ] [ 0 ∣ 1 ∣ 2 ∣ 3 ] f[i][0|1|2|3] f[i][0∣1∣2∣3]:走了i步&#xff0c;现在在j点的方案总数 转移&#xff1a; f [ i ]…

MySQL:从基础到实践(简单操作实例)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 下载前言一、MySQL是什么&#xff1f;二、使用步骤1.引入库2.读入数据 提交事务查询数据获取查询结果总结 下载 点击下载提取码888999 前言 在现代信息技术的世界…

Python实现MACD指标计算:股票技术分析的利器系列(1)

Python实现MACD指标计算&#xff1a;股票技术分析的利器系列&#xff08;1&#xff09; 介绍核心代码&#xff1a;EMA核心代码&#xff1a;MACD200 次交易日的收盘价格完整代码最终运行代码的效果展示DIFDEAMACD 介绍 先看看官方介绍&#xff1a; MACD (平滑异同平均线&#x…

寒假作业-day9

创建新项目 选择芯片 开启调试 配置时钟 时钟树的配置 工程管理 打开项目 编写代码

字节跳动官方出品AI,白嫖使用GPT4!

关注我&#xff0c;紧跟本系列专栏文章&#xff0c;咱们下篇再续&#xff01; 作者简介&#xff1a;魔都技术专家兼架构&#xff0c;多家大厂后端一线研发经验&#xff0c;各大技术社区头部专家博主&#xff0c;编程严选网创始人。具有丰富的引领团队经验&#xff0c;深厚业务架…

搜索专项---多源BFS模型

文章目录 矩阵距离 一、矩阵距离 本题思路: #include <bits/stdc.h>#define x first #define y secondtypedef std::pair<int,int> PII; constexpr int N1010;int n,m; char g[N][N]; int dist[N][N]; std::queue<PII> q;int dx[4] {-1, 0, 1, 0}, dy[4] …

8868体育助力西甲最新积分榜 皇马4球大胜稳坐榜一

西甲联赛第24轮的四场比赛于2月10日全面收官。其中&#xff0c;皇马在主场迎战吉罗纳队&#xff0c;以4-0的大比分击败对手&#xff0c;将领先优势扩大到5分&#xff0c;稳坐西甲榜首&#xff0c;掌握了争冠的主动权。 威尼修斯的世界波为皇马打开胜利之门&#xff0c;第6分钟就…

机器学习系列——(十四)正则化回归

引言 在机器学习领域&#xff0c;正则化回归是一种常用的技术&#xff0c;旨在解决过拟合问题&#xff0c;提高模型的泛化能力。本文将简单探讨正则化回归的概念、类型和应用&#xff0c;帮助读者更好地理解和运用这一重要技术。 一、概念 正则化回归是一种通过引入额外信息&…

【从零到Offer】MySQL最左匹配

前言 ​ 相信大家在日常开发时&#xff0c;也经常能听到“最左匹配”这个词&#xff0c;那么什么是最左匹配呢&#xff1f;本篇文章就带你一起探索“最左匹配”的神奇秘密。 什么是最左匹配 ​ 最左匹配&#xff0c;通常指的是最左前缀匹配原则&#xff0c;即MySQL在检索数据…

本地搭建three.js官方文档

因为three.js官网文档是国外的网站&#xff0c;所以你没有魔法的情况下打开会很慢&#xff0c;这时我们需要在本地搭建一个官方文档便于我们学习查看。 第一步&#xff1a;首先我们先访问GitHub地址 GitHub - mrdoob/three.js: JavaScript 3D Library. 下载不下来的小伙伴们私…

Offer必备算法06_位运算_十道力扣OJ题详解_由易到难

目录 位运算算法原理 ①力扣191. 位1的个数 解析代码 ②力扣338. 比特位计数 解析代码 ③力扣461. 汉明距离 解析代码 ④力扣136. 只出现一次的数字 解析代码 ⑤力扣260. 只出现一次的数字 III 解析代码 ⑥力扣面试题 01.01. 判定字符是否唯一 解析代码 ⑦力扣26…

day 20(补2.5)

fread 函数&#xff1a; 今日练习 C语言面试题5道~ 1. static 有什么用途&#xff1f;&#xff08;请至少说明两种&#xff09; 1) 限制变量的作用域 2) 设置变量的存储域 2. 引用与指针有什么区别&#xff1f; 1) 引用必须被初始化&#xff0c;指针不必。 2) 引用初始…

MYSQL学习笔记:MYSQL存储引擎

MYSQL学习笔记&#xff1a;MYSQL存储引擎 MYSQL是插件式的存储引擎 存储引擎影响数据的存储方式 存储引擎是用来干什么的&#xff0c;innodb和myisam的主要区别–数据存储方式----索引 mysql> show engines; ----------------------------------------------------------…

目标识别,跟踪模块

Tofu5m 是高性价比目标识别跟踪模块&#xff0c;支持可见光视频或红外网络视频的输入&#xff0c;支持视频下的多类型物体检测、识别、跟踪等功能。 产品支持视频编码、设备管理、目标检测、深度学习识别、跟踪等功能&#xff0c;提供多机版与触控版管理软件&#xff0c;为二次…

VLAN间通信

VLAN间通信的三种方法 vlanif接口 最常用&#xff0c;又叫虚拟接口&#xff0c;这种方式一般使用三层交换机实现&#xff0c;它包含路由模块和交换模块&#xff0c;交换模块可以实现剥离和添加VLAN标签,路由模块实现路由功能 VLANif接口 为各自vlan的网关 # interface Vlani…