java零基础教学篇之Stream流超级详细
Posted bug菌¹
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java零基础教学篇之Stream流超级详细相关的知识,希望对你有一定的参考价值。
👨🎓作者:bug菌
💌公众号:猿圈奇妙屋
🚫特别声明:原创不易,转载请附上原文出处链接和本文声明,谢谢配合。
🙏版权声明:文章里可能部分文字或者图片来源于互联网或者百度百科,如有侵权请联系bug菌处理。
一、前言🔥
环境:springboot2.3.1.RELEASE + jdk1.8
二、正文🔥
谈到Stream,你们第一眼想到了啥?I/O Stream?其实我在接触它之前看到第一眼也以为是io流那一块的东西,不是的哈。没有谁规定io就一定是io流,在java8中,由于Lambda的诞生与崛起,链式编程跑上热潮,从而也引入了一个对Stream的新概念,主要作用于解决当前集合类库的一些弊端。
1️⃣引言
传统集合中,可以说几乎所有的集合(Collection 或 Map等)都支持直接或间接的遍历操作。而我们对集合的操作除了必要的插入、删除、获取外,操作最多就是对集合进行遍历了。那么你有没有想过,循环遍历是否有啥弊端?
首先问大家个问题,为什么要使用循环?因为要遍历。但循环是遍历的唯一操作方式吗?遍历是对每一个元素逐一进行处理,而并不是从第一个到最后一个顺序处理的循环。总而言之就是前者是目的,而后者是方式。
再问大家一个问题,如果对集合中的元素进行有条件的筛选,你们打算怎么做?
我具体给大家设计个场景,比如一个集合stus中存储了全班同学的姓名,现在要求你把姓罗且两个字名称的同学名单筛选出来,可能在java8之前,大部分人可能会这么干!
1、传统for循环法:
具体演示代码如下:
@Test
public void test()
//存放全班同学姓名集合
List<String> stus = new ArrayList<>();
stus.add("罗志祥");
stus.add("罗成");
stus.add("黄忠");
stus.add("马超");
stus.add("后羿");
//存储指定数据集合
List<String> luoList = new ArrayList<>();
for (String name : stus)
if (name.length() == 2 && name.startsWith("罗"))
luoList.add(name);
//打印姓罗且名字两个字的同学姓名
for (String name : luoList)
System.out.print(name + " ");
代码运行截图:
很明显输出答案就唯有【罗成】符合题目要求,对吧。那看完上述这段代码,我们再来解析一下,它共有3个循环,分别解释如下:
- 循环1负责存放全班同学姓名。
- 循环2负责筛选出含有符合题意的姓名。
- 循环3打印出符合符合题意的姓名。
当我们需要对集合进行操作时,对集合进行遍历是避免不了的,而且一次还可能不够,循环再循环...那么是否有更优雅的实现方式呢?那么就用到今天我给大家提到的java8新特性之一Stream流。
2、Stream流写法
下面请大家看看,我是如何借助java8的Stream API来实现上述需求的,什么才叫简洁优雅。
具体演示代码如下:
@Test
public void test1()
//存放全班同学姓名集合
List<String> stus = new ArrayList<>();
stus.add("罗志祥");
stus.add("罗成");
stus.add("黄忠");
stus.add("马超");
stus.add("后羿");
//筛选+输出打印一步到位
stus.stream()
.filter(s -> s.startsWith("罗"))
.filter(s -> s.length() == 2)
.forEach(System.out::print);
代码运行截图:
仔细阅读代码,从获取流、过滤姓、过滤姓名长度、逐一打印。代码全程中并没有体现使用线性循环或者其他任何算法进行遍历,而我们真正要做的事内容被更好的在代码中进行体现。怎么样,感受到了Stream流的优雅了么,写法那是非常的简洁啊,绝绝子。
2️⃣流式思想概述
整体来看,流就类似工厂车间的“流水线”,当我们要对一个集合进行多步操作时,考虑到性能及整体便捷性,我们首要应该是拼好一个“流水线”模型,一步一步执行下去,比如:
如上这张图中展示了Stream流中的过滤、映射、跳过、计数多步操作后得出答案,这是对集合的一种处理方案,反过来看,方案就是“流水线”模型。
上图中每个框都是一个“流”,调用专门的方法,可以从一个流模型转换为另一个“流”模型。最终的3就是答案。
注意:Stream流就是一个集合元素的函数模型,它不是集合,也不是数据结构,其本身并不存储任何元素,请大家不要误解了。
Stream流是一个来自数据源的元素队列。其中数据源可以是一个集合或者数组等。和以前的Collection集合操作不同,Stream流它还有两个基础特征,具体如下:
- Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluentstyle)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代: 以前对集合遍历都是通过Iterator或者增强for的方式来处理, 显示的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式,流可以直接调用遍历方法。
当使用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)—> 数据转换—>执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。
三、获取流🔥
1️⃣根据Collection获取流
首先, java.util.Collection 接口中加入了default方法 stream 用来获取流,所以其所有实现类均可获取流。
如下我分别举例来获取Stream流,具体如下:
@Test
public void test2()
List<String> arrayList = new ArrayList<>();
//获取流
Stream<String> stream1 = arrayList.stream();
Set<String> hashSet = new HashSet<>();
//获取流
Stream<String> stream2 = hashSet.stream();
Vector<String> vector = new Vector<>();
//获取流
Stream<String> stream = vector.stream();
2️⃣根据Map获取流
java.util.Map 接口不是 Collection 的子接口,且其K-V数据结构不符合流元素的单一特征,所以获取对应的流需要分key、value或entry等情况:
具体演示如下:
@Test
public void test3()
Map<String, String> hashMap = new HashMap<>();
//获取流有以下几种
Stream<String> stream1 = hashMap.keySet().stream();
Stream<String> stream2 = hashMap.values().stream();
Stream<Map.Entry<String, String>> stream3 = hashMap.entrySet().stream();
3️⃣根据数组获取流
如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法of(),使用很简单:
具体演示代码如下:
/**
* 根据数组获取流
*/
@Test
public void test4()
String [] arr = new String[10];
Stream<String> stream = Stream.of(arr);
备注: of 方法的参数其实是一个可变参数,所以支持数组。
四、常用方法🔥
流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:
- 延迟方法:返回值类型仍然是 Stream 接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为延迟方法。)
- 终结方法:返回值类型不再是 Stream 接口自身类型的方法,因此不再支持类似 StringBuilder 那样的链式调用。本小节中,终结方法包括 count 和 forEach 方法。
1️⃣逐一处理:forEach
虽然方法名字叫 forEach ,但是与for循环中的[ for-each ]昵称不同,方法签名如下:
void forEach(Consumer<? super T> action);
该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理。
基本使用:
/**
* forEach 使用
*/
@Test
public void test5()
Stream<String> stream = Stream.of("a", "b", "c", "d");
//遍历打印
stream.forEach(name ->System.out.print(name));
2️⃣过滤:filter
可以通过 filter() 方法将一个流转换成另一个子集流。方法签名如下:
Stream<T> filter(Predicate<? super T> predicate);
该接口接收一个 Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。
基本使用:
Stream流中的 filter 方法基本使用的代码如:
/**
* filter 使用
*/
@Test
public void test6()
Stream<String> stream = Stream.of("a", "b", "c", "d");
//使用filter过滤得到一个新Stream
Stream<String> result = stream.filter(name -> name.startsWith("a"));
3️⃣映射:map
如果需要将流中的元素映射到另一个流中,可以使用 map 方法。方法签名如下:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
基本使用:
Stream流中的 map 方法基本使用的代码如:
/**
* map使用
*/
@Test
public void test7()
Stream<String> stream = Stream.of("1", "2", "3", "4");
//使用map得到一个新Stream
Stream<Integer> result = stream.map(name -> Integer.parseInt(name));
这段代码中, map 方法的参数通过方法引用,将字符串类型转换成为了int类型(并自动装箱为 Integer 类对象)。
4️⃣统计个数:count
正如旧集合 Collection 当中的 size 方法一样,流提供 count() 方法来数一数其中的元素个数,方法签名如下:
long count();
基本使用:
该方法返回一个long值代表元素个数(不再像旧集合那样是int值)。
/**
* count使用
*/
@Test
public void test8()
Stream<String> stream = Stream.of("1", "2", "3", "4");
//使用map得到一个新Stream
Stream<Integer> result = stream.map(name -> Integer.parseInt(name));
//获取个数
System.out.println(result.count());
5️⃣取用前几个:limit
limit 方法可以对流进行截取,只取用前n个。方法签名如下:
Stream<T> limit(long maxSize);
参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作。基本使用:
基本使用:
/**
* limit使用
*/
@Test
public void test9()
Stream<String> stream = Stream.of("1", "2", "3", "4");
//使用map得到一个新Stream
Stream<Integer> result = stream.map(name -> Integer.parseInt(name));
//limit截取
Stream<Integer> limit = result.limit(2);
//获取个数
System.out.println(limit.count());
6️⃣跳过前几个:skip
如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流。方法签名如下:
Stream<T> skip(long n);
如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。
基本使用:
/**
* skip使用
*/
@Test
public void test10()
Stream<String> stream = Stream.of("1", "2", "3", "4","5");
//使用map得到一个新Stream
Stream<Integer> result = stream.map(name -> Integer.parseInt(name));
//limit截取
Stream<Integer> limit = result.skip(2);
//获取个数
System.out.println(limit.count()); //3
7️⃣组合:concat
如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat()。方法签名如下:
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
注意:这是一个静态方法,与 java.lang.String 当中的 concat 方法是不同的。
基本使用:
/**
* concat使用
*/
@Test
public void test11()
Stream<String> stream1 = Stream.of("1", "2");
Stream<String> stream2 = Stream.of("a", "b");
//concat合并两个流
Stream<String> concatStream = Stream.concat(stream1, stream2);
//进行元素打印
concatStream.forEach(str ->System.out.print(str)); //12ab
实际运行结果截图:
如果你还想学习更多有关Stream流的知识点,你也可以看看我写的这期内容《Java8 StreamAPI的详细用法》,非常的详细,希望对有需要的同学有帮助。
... ...
好啦,以上就是本期的所有内容啦。如果对你有所帮助,还请不要忘记给bug菌[三连支持]哟。如果想获得更多的学习资源或者想和更多的技术爱好者一起交流,可以关注我的公众号『猿圈奇妙屋』,后台回复关键词领取学习资料、大厂面经、面试模板等海量资源,就等你来拿。
五、文末🔥
如果你还想要学习更多,小伙伴们大可关注bug菌专门为你们创建的专栏《java实战教学》,都是我一手打下的江山,持续更新中,希望能帮助到更多小伙伴们。
我是bug菌,一名想走👣出大山改变命运的程序猿。接下来的路还很长,都等待着我们去突破、去挑战。来吧,小伙伴们,我们一起加油!未来皆可期,fighting!
最后送大家两句我很喜欢的话,与诸君共勉!
☘️做你想做的人,没有时间限制,只要愿意,什么时候都可以start。
🍀你能从现在开始改变,也可以一成不变,这件事,没有规矩可言,你可以活出最精彩的自己。
💌如果文章对您有所帮助,就请留下您的赞吧!(#^.^#);
💝如果喜欢bug菌分享的文章,就请给bug菌点个关注吧!(๑′ᴗ‵๑)づ╭❤~;
💗如果对文章有任何疑问,还请文末留言或者加群吧【QQ交流群:708072830】;
💞鉴于个人经验有限,所有观点及技术研点,如有异议,请直接回复参与讨论(请勿发表攻击言论,谢谢);
💕版权声明:原创不易,转载请附上原文出处链接和本文声明,版权所有,盗版必究!!!谢谢。
以上是关于java零基础教学篇之Stream流超级详细的主要内容,如果未能解决你的问题,请参考以下文章
Ubuntu零基础教学-GParted磁盘分区工具使用|超级详细,手把手教学
Ubuntu零基础教学-Ubuntu20.04安装Gradle|超级详细,建议收藏!
Ubuntu零基础教学-Redis介绍及安装 | 超级详细,建议收藏