历史文章(累计400+篇文章)
你真的学会了Lambda表达式了吗?一篇让你学废了不香么 - 第417篇
悟纤:师傅上一节,你说要教我Stream来着,真的吗?
师傅:徒儿,你自己先学习过了没有?
悟纤:看过一些文章了,但是看完还是有点不知所以然。
师傅:Stream说简单也简单,说复杂也复杂。为师今天就来和你细说一下。
悟纤:好期待师傅的讲解哦~
导读:
在Spring Boot的框架的源码中会看到大量的Lambda和Stream,如果对于这两个特性不了解的话,那么看源码可能在某些代码上可能会看晕了,因此我们有必要来学习一下这两个特性。
Java8的两大大主要新特性Lambda表达式和Stream,两者提供了更高层次的抽象,简化开发,提高生产效率。
在前面的章节中,我们介绍了Lambda的概念以及详细的使用,这一节我们主要来看下Stream,当然在学习Stream中也会使用到Lambda。
一、Stream流
1.1 Stream简介
Stream是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。
在思路上,类似于SQL的存储过程,有几个步骤:
(1)先定义一些操作的集合,注意:这里只定义,不真正执行
(2)触发执行,获取结果
(3)对结果进一步处理,筛选、打印、使用
其中,第1步的定义操作叫惰性求值,给你套路(返回Stream),但是不会执行返回结果。
第2步的触发操作叫及早求值,这个人说干就干,立马要结果(返回结果数据)。
第3步的筛选类似SQL的where子句,对结果进一步的筛选。
对于Stream你还要知道的:
(1)Stream 不是集合,自己不会存储元素。
(2)Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
(3)Stream 操作是延迟执行的。必须搞清楚有哪些数据才能往下执行,这意味着他们会等到需要结果的时候才执行。
(4)Stream只能“消费”一次,如果想继续做其他操作,需要重新获取stream对象。
(5)更像一个高级的iterator,单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,但是可以并行化数据!
1.2 Stream API
Stream 类位于java.util.stream包下,在这里提供了很多常用的方法就是API,我们说的Stream API,也就是这些方法的集合,比如:Stream.of(构建一个Stream对象),Stream.map(对象关系的映射)等等。
1.3 Stream的理解
大家可以把Stream当成一个装饰后的Iterator。原始版本的Iterator,用户只能逐个遍历元素并对其执行某些操作;包装后的Stream,用户只要给出需要对其包含的元素执行什么操作,比如“过滤掉长度大于10的字符串”、“获取每个字符串的首字母”等,具体这些操作如何应用到每个元素上,就给Stream就好了!原先是人告诉计算机一步一步怎么做,现在是告诉计算机做什么,计算机自己决定怎么做。
1.4一个小栗子来理解Stream
我们看下这么一个需求,要统计集合中不为null的个数,在没有Stream的时候,我们会这么写代码:
List<Integer> nums = Arrays.asList(1,null,3,4,null,6);
//1.0版本 :使用for循环+count计数
long count = 0;
for(Integer num:nums){
if(num != null){
count++;
}
}
System.out.println("集合nums不为null的个数为:"+count);
使用Stream API的话,将会变得很简单:
List<Integer> nums = Arrays.asList(1,null,3,4,null,6);
// 2.0版本:使用Stream API
long count = nums.stream().filter(num -> num != null).count();
System.out.println("集合nums不为null的个数为:"+count);
上面这段代码是获取一个List中,元素不为null的个数。这段代码虽然很简短,但是却是一个很好的入门级别的例子来体现如何使用Stream,正所谓“麻雀虽小五脏俱全”。我们现在开始深入解刨这个例子,完成以后你可能可以基本掌握Stream的用法!
图片就是对于Stream例子的一个解析,可以很清楚的看见:原本一条语句被三种颜色的框分割成了三个部分:
(1)红色框中的语句是一个Stream的生命开始的地方,负责创建一个Stream实例;
(2)绿色框中的语句是赋予Stream灵魂的地方,把一个Stream转换成另外一个Stream,红框的语句生成的是一个包含所有nums变量的Stream,进过绿框的filter方法以后,重新生成了一个过滤掉原nums列表所有null以后的Stream;
(3)蓝色框中的语句是丰收的地方,把Stream的里面包含的内容按照某种算法来汇聚成一个值,例子中是获取Stream中包含的元素个数。
总的来说,使用Stream的常规的3大曲就是:
创建Stream·中间操作·终止操作。
一般都需要这3个步骤,看源码的时候,就按照这三个步骤进行阅读即可。
注意点就是:这里需要注意的是中间操作可以是一个链条(可能是0),也就是可是使用filter之后,然后再使用map。
二、Stream API使用
我们还是具体来看一些例子来理解Stream API的使用吧,看多了也就懂了。讲再多的知识点,不如动手来点实例说的清楚。
2.1 Stream.of(T… t)
要使用Stream,那就必须得先创建一个Stream,比如:
Stream<String> StrStream = Stream.of("a", "b","c");
当然这里是直接使用Stream的api创建的,在实际的代码中,我们的集合会有相应的方法进行构建Stream,比如List:
List<String> words = Arrays.asList(new String[]{"a","b","c"});
Stream<String> wordsStream = words.stream();
2.2 Stream.collect(Collector<?super T, A, R> collector)
使用收集器Collector将StrStream转化为熟悉的集合Collection:
Stream<String> StrStream = Stream.of("a", "b","c");
List<String> list = StrStream.collect(Collectors.toList());
//输出结果:[a, b, c]
System.out.println(list);
2.3 Stream.map(Function<? superT, ? extends R> mapper)
所谓map,从字面理解就是映射。这里指的是对象关系的映射,看下例子:
//从小写字母映射到大写字母:
List<String> words = Arrays.asList("a","b","c");
List<String> newWords = words.stream().map(word->word.toUpperCase() ).collect(Collectors.toList());
//输出结果:[A, B, C]
System.out.println(newWords);
2.4 Stream.filter(Predicate<? super T>predicate)
filter顾名思义,过滤筛选。这里的参数函数接口是一个条件,筛选出满足条件的元素:
//筛选出以 j 字母开头的元素个数,此例中的count方法也是终止操作,是为了计算出 Stream 中的元素个数,
List<String> languages = Arrays.asList("java","javascript","php","python");
long count = languages.stream().filter( str->str.startsWith("j") ).count();
//输出结果:2
System.out.println(count);
具体的还有很多方法,大家根据需要自行学习。
三、Stream API之Filter代码拆解
为了让大家对于Stream的这个使用有一个更深的理解,这里使用拆解的方式对于上面的一个例子“筛选出以 j 字母开头的元素个数”详细的说明一下:
3.1 需求说明
有这么一组数据"java","javascript","php","python":筛选出以 j 字母开头的元素个数。
3.2 1.0:一般的写法
一般的写法就是for遍历,然后进行统计:
//筛选出以 j 字母开头的元素个数,此例中的count方法也是终止操作,是为了计算出 Stream 中的元素个数,
List<String> languages = Arrays.asList("java","javascript","php","python");
long count = 0;
for(String str:languages){
if(str.startsWith("j")){
count++;
}
}
//输出结果:2
System.out.println(count);
3.3 2.0:Stream的写法
使用Stream可以获取流对象,然后对流进行操作,也就是Stream中提供的API:
//筛选出以 j 字母开头的元素个数,此例中的count方法也是终止操作,是为了计算出 Stream 中的元素个数,
List<String> languages = Arrays.asList("java","javascript","php","python");
Stream<String> oldStream = languages.stream();//1.构建流
Stream<String> newStream = oldStream.filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.startsWith("j");
}
});//2.中间操作
long count = newStream.count();//3.终止操作
//输出结果:2
System.out.println(count);
3.4 3.0:Stream+Lambda的写法
这里我们发现Predicate是一个接口,在此接口上注解了@FunctionalInterface,那么就支持Lambda的写法:
那么就可以对其进行优化:
//筛选出以 j 字母开头的元素个数,此例中的count方法也是终止操作,是为了计算出 Stream 中的元素个数,
List<String> languages = Arrays.asList("java","javascript","php","python");
Stream<String> oldStream = languages.stream();//1.构建流
Stream<String> newStream = oldStream.filter( s->s.startsWith("j") );//2.中间操作
long count = newStream.count();//3.终止操作
//输出结果:2
System.out.println(count);
在上面我们定义了很多的中间变量,其实没什么鸟用,直接合成一句代码即可:
//筛选出以 j 字母开头的元素个数,此例中的count方法也是终止操作,是为了计算出 Stream 中的元素个数,
List<String> languages = Arrays.asList("java","javascript","php","python");
long count = languages.stream()//1.构建流
.filter( s->s.startsWith("j") )//2.中间操作
.count();//3.终止操作
//输出结果:2
System.out.println(count);
这个乍一看不就是链式编程吗,只是加入了lambda的链接编程,让代码变得有点复杂了,如果是直接的set属性的方式,那么就很好理解了。
最后把代码写成一行,就是最终的结果了:
//筛选出以 j 字母开头的元素个数,此例中的count方法也是终止操作,是为了计算出 Stream 中的元素个数,
List<String> languages = Arrays.asList("java","javascript","php","python");
long count = languages.stream().filter( s->s.startsWith("j") ).count();
//输出结果:2
System.out.println(count);
结束语
相信通过这一节的讲解,对于Lambda和Stream有了一个比较深刻的理解,更多的还是需要自己去敲敲代码以及看更多的代码来加深自己对于这两个特性的认知。
我就是我,是颜色不一样的烟火。
我就是我,是与众不同的小苹果。
à云课堂学院:悟空学院
学院中有Spring Boot相关的课程!!
SpringBoot视频:从零开始学Spring Boot Plus - 网易云课堂
SpringBoot交流平台:https://t.cn/R3QDhU0
SpringSecurity5.0视频:权限管理spring security - 网易云课堂
ShardingJDBC分库分表:分库分表Sharding-JDBC实战 - 网易云课堂
分布式事务解决方案:分布式事务解决方案「手写代码」 - 网易云课堂
JVM内存模型调优实战:深入理解JVM内存模型/调优实战 - 网易云课堂
Spring入门到精通:Spring零基础从入门到精通 - 网易云课堂
大话设计模式之爱你:大话设计模式之爱你一万年 - 网易云课堂