通过前面两篇文章,大致学习了一下Lamda表达式,接下来我们简单学习一下Stream API 的使用。这是学习lamda的目的。
按照惯例,我们先把必要的理论知识贴在前面,以便于随时实践和理论知识相印证。
一:什么Stream API?
Stream是一组用来处理数组、集合的API。
二:Stream特性有哪些?
1):不是数据结构,没有内部存储
2):不支持索引访问
3):延迟计算
4):支持并行
5):很容易生成数组或集合(List,Set)
6):支持过滤,查找,转换,汇总,聚合等操作
三:Stream运行机制是怎样的?
Stream分为 源source,中间操作,终止操作。流的源可以是一个数组、一个集合、一个生成器方法,一个I/O通道等等。一个流可以有零个和或者多个中间操作,每一个中间操作都会返回一个新的流,供下一个操作使用。一个流只会有一个终止操作。Stream只有遇到终止操作,它的源才开始执行遍历操作。
四:常用API有哪些?
1)中间操作:
过滤 filter
去重 distinct
排序 sorted
截取 limit、skip
转换 map/flatMap
其他 peek
2)终止操作
循环 forEach
计算 min、max、count、 average
匹配 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny
汇聚 reduce
收集器 toArray collect
上面简单简单罗列了一下stream的一些概念性的知识,先留个印象,下面我们来举一些实例,巩固熟悉一下api用法。
首先stream如何生成?stream可通过数种方式生成,很灵活,根据场景使用相应的方式生成。
注释应该蛮清楚了,这里给出五种方式创建源Stream.得到了stream之后,我们继续学习中间操作过程以及终止操作。
(一)中间操作
中间操作包括过滤 filter,去重 distinct,排序 sorted,截取 limit、skip,转换 map/flatMap,其他 peek。我们着重讲一下过滤、排序和转换还有peek。
知识点一:filter
注意上述代码中的limit截取操作顺序,在fiilter之后表示是筛选之后截取20个,如果是在filter之前则表示对原始结合取20个然后再近些筛选,这是个小知识点注意一下。
我们继续,filter传入的参数是Predicate函数式接口,他的主要作用就是提供一个test方法,接受一个参数返回一个布尔类型,Predicate在stream api中进行一些判断的时候非常有用。除test方法之外,Predicate还提供了另外三个默认方法和一个静态方法。这个函数式接口很重要,我们通过代码来熟悉了解一下。
知识点二:Comparator接口与Comparable接口
我们的需求二中是求集合中最大值、最小值,集合个数以及平均值。我们发现在求最大值和最小值中要求传入的参数为Comparator接口。包括我们稍后讲到的stream接口中sorted方法中其中一个重载方法也是需要传入comparator接口作为参数。Comparator接口其实平时在我们工作中有时还是会用到的。我们重新学习一下。我们熟知的treeset是内部排序,原因是什么呢?我们来看下例:
我们可以看到输出是排序输出的。原因是什么呢?奥秘就在于User2这个实体类实现了Comparable接口
我们在做排序功能时还可以使用Collections工具类的sort方法,他有两种重载方法,一种是要求传入集合的子元素必须实现Comparable接口,类似上述排序,第二种无须实现Comparable接口,但是第二个参数必须是Comparator接口的子类型(需要重写compare方法实现元素的比较),看下例:
知识点三:sorted
我们刚刚重温了一下Comparator接口,中间操作sorted传入的参数就是Comparator接口的实现类,看下例
知识点四:map/flatmap
map和flatmap主要是转换,举个最简单的例子,将人名首字母大写,我们可以简单的作如下实现:
是的,我们通过map函数将传入的每个姓名处理后打印,这是个转换过程。
我们简单了解了一下map转换函数的使用,那么flatmap函数怎么使用以及和map函数之间有什么区别呢?我们继续上代码,
现在有一个需求,我们需要统计所有人的爱好一共有哪些(去重)?想实体类很简单,如下
由于爱好不能重复,因此使用set容器承载。回到正题,我们要如何实现统计出所有人的爱好呢?下面给出两种写法
有发现区别么?我们看到第一种写法是直接使用flatMap函数,传入实体类对象,对象得到爱好的集合,然后将这个集合流化,最后去重,第二种方式是先使用map函数得到爱好的集合,然后使用flatmap函数将集合流化。我们基本可以得出结论map返回的任意对象类型,flatmap返回的必须是流。我们分别看下看下源码map和flatmap源码:
也许下面这个例子更能清楚的回答此两种方法的区别
知识点五:peek
peek官方的解释是生成一个包含原Stream的所有元素的新Stream,同时会提供一个消费函数(Consumer实例),新Stream每个元素被消费的时候都会执行给定的消费函数;
那这个函数有什么用处呢,目前我只能想到将他作为调试使用以及可以作为中间缓存或者日志记录操作。如下例
我们可以看到peek方法得到的数据是每个被消费的元素,上例中skip了2个元素,limit了2个元素,总共四个元素。如果元素没被消费,peek中的函数是不执行的。也就是说他是中间操作而非终止操作。
(二)终止操作
终止操作 :循环 forEach、 计算 min、max、count、 average、 匹配 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、汇聚 reduce收集器 toArray collect。
前面其实我们已经用到了很多终止操作(废话,stream是延时操作,没有终止操作,则不会执行),比如循环 forEach、 计算 min、max、count、 average等。那么让我们来学习一下其他的操作吧:
知识点一:匹配 anyMatch、 allMatch、 noneMatch
我们先看方法定义:
anyMatch表示判断Stream中的是否有满足指定条件的元素。如果最少有一个满足条件返回true,否则返回false。
allMatch表示判断Stream中的元素是否全部满足指定条件。如果全部满足条件返回true,否则返回false。
noneMatch表示判断Stream中的所有元素是否满足指定的条件,如果所有元素都不满足条件,返回true;否则,返回false。
值得注意的是上面三个方法传入的参数都是Predicate接口(断言),上面我们已经对他有所了解,此处就不过多解释。我们来举个栗子,看下如何使用这三个方法:
上面例子很简单,就是判断流中的元素是不是有大于三的,如果有则返回true。我们看到结果了,是true。注意看结果,有没有发现这里面有个很有趣的事情,我们使用limit函数传入5,正常来说流里面应该是1,3,5,7,9这五个元素,这应该是没有异议的。但是我们使用peek函数为什么只打印了3个呢?其实原因就是,上面我们已经学习了peek函数,知道了peek函数传入的参数是每个被消费的元素,那么反过来我们是不是可以得到这么一个结论,anymatch比较时,按照顺序进行判断,一旦返回true,则不继续往下判断了(个人见解,不知道对不对,希望知道的小伙伴指正啊),因此只消费了三个元素,判断到5>3的时候就终止了。
我们继续看下allMatch
我们通过结果很容易反推allMatch是一旦判断到有元素不满足条件,即返回false是时候则终止判断
那么noneMatch呢
结果是不是跟anyMatch类似?我觉得可以这么解释,noneMatch也是元素一个一个比较,一旦发现满足条件的,则终止判断,并返回false。
知识点二: Optional
我们在使用findFirst、 findAny这两个方法之前,先学习一个类:Optional类。我们先看下类结构
方法并不多,我们拎出其中几个关键。
1)public static Optional of(T value):创建Optional对象,如果传入的对象为空,则会报空指针异常。
2) public static Optional ofNullable(T value):创建Optional对象,如果传入的对象为空,则会创建一个空的Optional类对象,看源码
那么此时我们有个疑问,为什么不一劳永逸的使用ofNullable方法呢?在实际场景中,我们可能需要obj必须不为空,为空则报错的时候,我们则要选择of方法,这样可以避免这个错误被隐藏,从而导致隐患。
3) public T orElse(T other):如果有值则返回该值,否则返回传递给它的参数值。
4)public T orElseGet(Supplier<? extends T> other):有值的时候返回值,如果没有值,它会执行作为参数传入的 Supplier(供应者) 函数式接口,并将返回其执行结果.
看上去这两个方法好像差不多,其实是有区别的。看下例代码
我们可以发现,当Optonal的值是空值时,无论orElse还是orElseGet都会执行;而当返回的Optional有值时,orElse会执行,而orElseGet不会执行。
5)public boolean isPresent() 判断值是否存在,此方法不建议使用,一般使用下面这个ifPresent方法,因为这个方法就相当于return null==obj
6) public void ifPresent(Consumer consumer) 如果值不为空则做些什么。
7)public <U> Optional<U> map(Function <?super T, ?extends U> mapper)
8)public <U> Optional<U> flatMap(Function <?super T, Optional<U>> mapper)
上面两个方法是不是很熟悉?是的stream类中也有这两个方法,很类似,用心体会一下他们其中的区别。用大白话就是,map他在内部已经封装好了optional对象,而flatmap则需要自己封装。在stream类中是不是也很类似。
为了加深对optional的理解,我们举个栗子。需求:获取街道名称。我们先建立一个place实体类:
这个地点的实体类包含国家、城市、街道,这样就定位到了一个地点。如果是java8之前的写法,我们要获取街道名称需要进行一层一层的空值判断,如果在流式写法下就相当的流畅了:
如果发生了异常,我们要详细记录是哪个节点发生的异常,那怎么做呢?一种做法是(我也不知道好不好,也许有更优解决办法。)
上述代码其中经过调试,我对流式操作有了更进一步的理解。当我们将国家置空时,“城市获取”和“街道获取”不会打印,充分说明了流被截断了。其实对这段代码我还有一处疑问:不知道为啥要用异常数组Exception[ ] e(如果是Exception e则代码编译不通过,修复成异常数组才行),知道的小伙伴可以留言哈,小弟感激不尽!
知识点三:findFirst、 findAny
待续。。。
网友评论