在介绍流式思想是什么之前,我们先来感受一下Stream流的使用的便利,下面以集合遍历为例子进行介绍
众所周知,如果想要对一个集合进行遍历,那么我们可以采用普通for循环、增强for循环和迭代器,这里以增强for循环为例子,另外,当我们想要对集合的数据进行过滤的时候,第一个想到的便是if语句,现在我们的需求是,有一个储存了多个个人名字与性别的数组,我要从中筛选出名字是由3个字母组成的女生并打印其信息,下面来看看实现该功能的示例程序
其运行结果
这里先不介绍Stream流的用法,先感受一下实现上述功能的代码是怎么样子的
其运行结果是一样的
是不是感觉代码变简单了,不需要for循环,也不需要if判断语句,这就是采用Stream流的方式来实现,下面来详细介绍一下什么是Stream流与流式思想
流式思想的实质其实跟流水线操作参不多,流水线上有众多的产品,当他经过每个工位的时候需要执行不同的操作,最后输出成品,而这里我们所介绍的Stream流可以理解为流水线上的产品,是不是很抽象?来看个图
Stream流其实是一个集合元素的函数模型,他不是集合,也不是数据结构,他本身也并不储存任何元素或者是地址值,当使用一个流的时候,通常包括三个基本步骤:获取一个数据源 --> 数据转换 --> 执行操作获取想要的结果
,每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换),这样就允许对其操作像流水线一样
下面就来了解一下Stream流的获取方式和常用方法
获取Stream流有两种方式:
stream
来获取流public default Stream<E> parallelStream():返回可能并行的 Stream与此集合作为其来源。
of
获取相应数组的流public static <T> Stream<T> of(T... values):返回其元素是指定值的顺序排序流。
public static <T> Stream<T> of(T t) :返回包含单个元素的顺序 Stream 。
这里分别演示一下两种不同的Stream流获取方式
流类型的操作很丰富,下面介绍的是常用的方法,这些方法可以分成两种:
count
和forEach
方法为什么要先给你们介绍一个终结方法呢?因为后续其它方法的学习我们都要利用到终结方法来查看结果
public void forEach(Consumer<? super T> action):对此流的每个元素执行操作。
可见,forEach
方法所需要传递的参数是上篇文章中介绍的一个Consumer函数式接口,这是一个消费型接口,换言之,forEach
方法会把每一个流元素交给Consumer接口来消费,这里我们来演示一下对数据直接打印输出
其运行结果
filter
方法,相信你也能猜到他是一个流元素的过滤器
public Stream<T> filter(Predicate<? super T> predicate):返回由与此给定谓词匹配的此流的元素组成的流。
毫无悬念,这个方法所传递的又是上篇文章中介绍的Predicate接口,这里我们来演示一下把个人信息数组中姓名首字母为J的个人信息打印出来
其运行结果
说起map
是不是会想起双列集合Map呢?其实这个map
方法也有一点点那么个意思
public<R> Stream<R> map(Function<? super T,? extends R> mapper)
返回由给定函数应用于此流的元素的结果组成的流。
可见,这个方法所需要传递的是一个Function接口,换言之就是把流中的元素映射到另外一个流上,可以理解为可进行数据转换,这里我们演示一下把字符串格式的数字转换为整形,为了证明转换后的是整形数据,输出时对其进行 +1输出(字符串不能+1)
其运行结果
你们一定想到这个方法是计数的对不对?那么既然都计数了,返回值肯定不是Stream了,因此这是一个终结方法
public long count() :返回此流中的元素数。
limit
即是限制,就是可以理解为我只要前几个元素
public Stream<T> limit(long maxSize):返回由此流的元素组成的流,截短长度不能超过 maxSize 。
注意,如果集合当前长度大于参数则进行截取操作,否则不进行操作,下面来看演示
其运行结果
这个方法你们也会想到跳过对不对
public Stream<T> skip(long n) :在丢弃流的前n个元素后,返回截取后的流。
注意,如果流的当前长度大于n,则跳过前n个,否则会得到一个长度为0的流,下面看一下演示
其运行结果
还记得String类中也有一个concat
方法用于字符串拼接吗?这里的concat
方法也是拼接的作用
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b):创建一个懒惰连接的流,其元素是第一个流的所有元素,后跟第二个流的所有元素。
我们之前在学习过程中一直都是调用某个方法去完成某些功能,即方法调用,那么方法引用又是什么东西呢?
双冒号::
为引用运算符,而它所在的表达式也被称为方法引用。如果Lambda要表达的函数方案已经在某个方法中实现,那么则可以通过双冒号来引用该方法作为Lambda的替代者,说白了,这其实又是对Lambda表达式的一个优化
似乎有点抽象,到底是什么意思呢,下面给出两种写法,其实是完全等效的:
s -> System.out.println(s)
System.out::println
别的不说,从形式上看起来是不是已经是简洁很多呢?细心的同学可能也发现了前文介绍Stream流的方法中我给出的示例程序的打印语句s -> System.out.println(s)
都是不同颜色的,这其实就是IDEA在提示我们可以用方法引用对其进行优化
例如map
方法中的程序优化前是这样的
优化后是这样的
细心的同学又发现了,为什么这里的System.out.println
是不可以用方法引用进行优化呢?发现了吗?为什么呢?因为这里传递进去Lambda表达式的是p,而我想要打印的是p+1,严格意义上来说,它们不是同一个东西,换言之,当它们不是同一个东西的时候,就不能进行推断
再说明白一点,还是以打印语句为例子,在Lambda表达式写法中是s -> System.out.println(s)
,其意思是我把传进来的字符串命名为s,然后打印命名为s的字符串,则这个s其实就是一个形参,可以说没有意义,那么其实我只需要知道你调用了谁(System.out
)的什么方法(println
)来处理那个传递进来的东西就好了,我就不需要再写一个形式参数那么麻烦了,因此就有了方法引用的简化System.out::println
,无论你传递什么进来我都用System.out
中的println
来对其进行处理,也正是因为如此,所以传递进来的参数一定要是方法引用中的方法可以接受的类型,否则会抛出异常,在这个表达式中双冒号前的就是被调用的对象,双冒号后的就是方法
我相信我已经说得很明白了,如果还不太懂的同学也不用怕,你只需要知道是个什么意思就好了,如果确实要写的话,你可以先写个Lambda表达式,IDEA可以给你自动转过去(手动狗头)
好啦,这篇文章结束啦啦啦啦啦啦~