Stream流
说到Stream便容易想到I/O Stream,而实际上,谁规定“流”就一定是“IO流”呢?在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。
Stream流的思想
- Stream流,我们可以看成是一条生产线,我们要对多个元素完成多个目标时,可以把所有元素当作一个流模型,搭建一条流,流中可以制定对流模型执行一个步骤(筛选,映射,计数,跳过等等),完成步骤后,得到新的流模型,新的流模型执行下一步操作。
- 这个我们制定的Stream流,也称为“函数模型”,每一步操作,就是将一个流模型转换为另一个流模型。
Stream流的特点
- 1.Stream不会修改源数据。(执行的是新创建的流模型,不会对源数据进行修改)
- 2.Stream流的部分操作具有延时性。(当只有流有终结方法的时候,流才会被执行)
- 3.Stream不能存储元素
- 4.Stream流是单向的
执行Stream流通常包括三个基本步骤:获取一个数据源→数据转换→执行操作获取想要的结果
获取流
获取流非常简单,我们这里只讲集合和数组获取流的方式
Collection获取流
Collection接口中有一个默认(default)方法stream来获取流,所以其所有实现类(所有单列集合)都可以通过这个方法来获取流对象
格式:
单列集合对象名.stream() //该方法会返回一个对应的流
Map集合获取流
Map集合不能直接获取流,只能通过keySet或者entrySet方法得到Map集合的键对象或者键值对对象的Set集合后,再调用他们的Stream来获取流
格式:
Map集合对象名.keySet().Stream() //返回一个map集合键的流
Map集合对象名.entrySet().Stream() //返回一个map集合键值对的流
数组获取流
如果我们要获取数组的流,由于数组对象不可能添加默认方法,所以Stream接口中提供了静态方法of
注意:基本数据类型数组不能使用流
格式:
stream.of(数组名) //返回一个该数组的对应流
常用方法
Stream流内的方法分为两种
- 终结方法:返回值类型不再是Stream接口自身类型的方法,因此后续不在支持stream流的链式调用,本小节中,终结方法包括count和forEach方法。
- 延迟方法:返回值类型仍然是Stream接口自身类型的方法,因此支持后续链式调用(除了终结方法外,其余方法均为延迟方法。)
filter(过滤 )
该方法可以将一个流转换成另一个子集流(延迟方法)
方法签名:
Stream<T> filter(Predicate<? super T> predicate);
该方法会接收一个predicate(判断接口)接口对象(可以是Lambda或方法引用)作为筛选条件,如果流内元素满足该接口的判断条件,返回的值是true,就会留到新的stream流中,否则就会舍弃。
格式:
Stream流对象.filter(lambda表达式) //每个元素依次执行lamdba表达式,满足的就留下来,形成一个新的流对象
count(统计个数)
count该方法是终结方法
正如旧集合Collection当中的size方法一样,流提供count方法来数一数其中的元素个数
方法签名:long count()
该方法会返回一个long值,代表流内元素的个数。
limit(取用前几个 )
limit方法可以对流进行截取,只取用前面n个,返回一个新的stream流(延迟方法)
方法签名:Stream<T> limit(long maxSize); //返回一个只去前面maxSize个元素的新流
注意:如果截取长度超过流的元素个数,默认不执行
skip(跳过前几个 )
如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流(延迟方法)
方法签名:Stream<T> skip(long n); //返回一个去掉前面n个元素的新流
注意:如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流
map(映射 )
如果需要将流中的元素映射到另一个流中,可以使用map方法(延迟方法)
方法签名:<R> Stream<R> map(Function<? super T, ? extends R> mapper); //该方法会得到一个新的stream流,具体是什么取决于你写的lambda
该方法会通过给每个元素调用Function(转换接口)接口内的apply方法,每个元素执行后,得到一个新的元素,存入新的流
concat(组合 )
如果有两个流,希望合并成为一个流,那么可以使用Stream接口的静态方法concat
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) 该流会将两个流合并
forEach(逐一处理 )
- forEach该方法是终结方法
- 虽然方法名字叫forEach,但是与for循环中的“for-each”昵称不同,该方法并不保证元素的逐一消费动作在流中是被有序执行的。
方法签名:void forEach(Consumer<? super T> action); //该方法会消费掉流的元素,没有返回值
该方法接收一个Consumer(消费接口)接口函数,会将每一个流元素交给该函数进行处理
并发流
当需要对存在于集合或数组中的若干元素进行并发操作时,我们需要仔细考虑多线程环境下的原子性,竞争身子锁问题,而这对Stream流来说,很简单
转换为并发流
Stream的父接口java.util.stream.BaseStream中定义了一个parallel方法,我们直接使用Stream流对象调用一下即可变身成为支持并发操作的流,其返回值仍然为Stream类型
例:
单列集合对象.stream().parallel() //该流已经支持并发操作
直接获取并发流
在通过集合获取流时,也可以直接调用parallelStream方法来直接获取支持并发操作的流
方法签名:default Stream<E> parallelStream()
单列集合对象.parallelStream() //会得到一个支持并发的Stream流
使用并发的结果
流变为支持并发后,就会变得和多线程一样,多个流对象会进行抢夺Cpu操作
例如,多次执行下面这段代码,结果的顺序在很大概率上是不一样的
Stream.of(10, 20, 30, 40, 50) .parallel() .forEach(System.out::println);
收集Stream结果
对流操作完成之后,如果需要将其结果进行收集,例如获取对应的集合、数组等,我们可以使用下列几种方法
收集到集合中
Stream流提供collect方法,可以将流对象收集存储到集合中返回,该方法需要传入一个Collector接口对象来指定收集到哪种集合中,我们可以使用Collectors类的静态方法创建我们想要的接口对象
格式:
流对象.collect(Collector对象) //返回一个Collector指向的对应集合
Collectors.toList //返回一个指向List集合的Collector对象
Collectors.toSet //返回一个指向Set集合的Collector对象
收集到数组中
Stream提供toArray方法来将结果放到一个数组中,由于泛型擦除的原因,返回值类型是Object[]的
格式:
流对象.toArray //返回一个Object[]类型的数组
有了Lambda和方法引用之后,可以使用toArray方法的另一种重载形式传递一个IntFunction<A[]>的函数,继而从外面指定泛型参数。
方法签名:<A> A[] toArray(IntFunction<A[]> generator);
有了它,我们得到的数组可以不用局限于Objcte
示例(收集一个String类型数组):
Stream<String> stream = Stream.of("10", "20", "30", "40", "50");
String[] strArray = stream.toArray(String[]::new); //引用想要类型的构造器,就能得到指定类型的数组