Stream流
概述
Stream是一种用来操作数据的流程方法,通常使用流来操作集合数据,支持并发操作。
流是一个强化的迭代器,具有更强大的操作功能,一切全凭想象力。
Stream实例的获取/创建
通过静态方法empty()创建空流
var s = Stream.empty();
通过集合的stream()/parallelStream()方法创建
var list = new ArrayList();
var s1 = list.stream();
var s2 = list.parallelStream();
通过静态方法of()创建
var s = Stream.of("test","me");//不可以放入null元素,如放入会导致运行错误空指针异常
var s = Stream.of("test","me","null");//可以放入null元素
通过静态方法iterate方法创建
Stream.iterate(0,n->n+1).forEach(System.out::println);
//第一个参数是起始值,第二个是对n进行操作后存入数组,会往流中不停放入数据,需要结合limit使用
Stream.iterate(0,n->n<81,n->n+1).forEach(System.out::println);
//第一个参数是起始值,第二个参数相当于循环结束条件,第三个是对n进行操作后存入数组
//感觉怪怪的也不知道有什么卵用
通过静态方法generate()方法创建
//会不停的根据Supplier方法不停的创造实例并加入流中,且生成的是无序队列
var s = Stream.generate(Supplier s);
//上述代码不会被立刻执行,而是在遇到末端方法之后才会开始执行
Stream.generate(()->Math.random()).forEach(System.out::println);
//上述代码会不停的打印0-1的随机数
//一般generate()会和limit()搭配使用,以免程序进入死循环,代码示例如下
Stream.generate(()->Math.random()).limit(5).forEach(System.out::println);//打印前五个流中数据
通过数组获得流
int[] arr = new int[]{1,2,3};
IntStream = Arrays.stream(arr);//只要是数组均可以使用这个工具类方法去返回流,只要确定好接收器就可以了
通过Concat拼接两个流得到新的流
Stream s3 = s1.concat(s2);
流类方法的四大类型
- 中间方法:对流数据进行操作后重新返回一个流数据,这是一个延迟方法,不会立即执行,只有后续继续调用末端方法才会开始执行。
- 终端方法:只可使用一次,使用后流作废,返回一个数据
- 有状态的方法:该方法给流增加新属性,如元素的唯一性、元素的最大数量,资源占用大
- 短路方法:短路方法可以尽早结束对流的操作,不必检查所有的元素
Stream的常见中间方法
| 方法名 | 说明(以下代码示例中s1均表示一个引用型流对象) |
|---|---|
| sequential | 返回一个相等的串行的Stream对象,如果原Stream对象已经是串行就可能会返回原对象 |
| parallel | 返回一个相等的并行的Stream对象,如果原Stream对象已经是并行的就会返回原对象 |
| unordered | 返回一个不关心顺序的Stream对象,如果原对象已经是这类型的对象就会返回原对象 |
| onClose | 返回一个相等的Steam对象,同时新的Stream对象在执行Close方法时会调用传入的Runnable对象 |
| close | 关闭Stream对象 |
| filter (Predicate<? super T> predicate) | 元素过滤:对Stream对象按指定的Predicate进行过滤,返回的Stream对象中仅包含未被过滤的元素 //代码示例 s1.filter(ele->ele.length()>4);//返回一个新的流,流中包含原流中长度大于4的字符串 |
| map (Function<? super T,? extends R> mapper) | 元素一对一转换:使用传入的Function对象对Stream中的所有元素进行处理,返回的Stream对象中的元素为原元素处理后的结果 //代码示例 s1.filter(ele->ele.length());//对流中所有元素一一进行映射处理,返回一个新的流 |
| mapToInt (ToIntFunction<? super T> mapper) | 元素一对一转换:将原Stream中的使用传入的IntFunction加工后返回一个IntStream对象 此方法用方法类似map不再赘述,还有mapToLong/mapToDouble方法返回对应XXXStream |
| flatMap | 元素一对多转换:对原Stream中的所有元素进行操作,将原流中元素转为新的流,每个元素会有一个或者多个结果,然后将返回的所有子流拼接成成一个统一的流并返回; //官方示例代码 orders.flatMap(order -> order.getLineItems().stream());//返回一个包含所有产品的流 其他代码示例 Integer[] arr = new Integer[] {1,3,5}; Integer[] arr2 = new Integer[] {2,4,6}; Stream flatMap = of.flatMap(a ->Arrays.stream(a)); //如果被扁平化的是一个文件,那么将会读取文件中的单词 Stream lines = Files.lines(path, StandardCharsets.UTF_8); Stream words = lines.flatMap(line -> Stream.of(line.split(" +"))); |
| distinct | 去重:返回一个去重后的Stream对象 假设存在一个流s1,保存了1,1,2,3四个数据 s1.dinstinct();//基于equals方法判断是否相同,返回一个流,包含1,2,3三个数据 |
| sorted | 排序:返回排序后的Stream对象,升序排序 |
| sorted()(Comparator<? super T> comparator) | 使用指定的比较器进行排序。 |
| peek | 允许执行末端操作后,返回未执行末端操作前的流,主要用于调试请看官方示例 Stream.of(“one”, “two”, “three”, “four”) .filter(e -> e.length() > 3) .peek(e -> System.out.println("Filtered value: " + e)).map(String::toUpperCase).peek(e -> System.out.println("Mapped value: " + e)).collect(Collectors.toList()); |
| limit | 获取有限个元素组成新的Stream对象返回,主要搭配无限流使用 Stream.genarate(Supplier s).limit(10);只允许操作无线流的前十个数据 抛出异常: IllegalArgumentException - if maxSize is negative |
| skip | 抛弃前指定个元素后使用剩下的元素组成新的Stream返回,参考limit使用 |
| takeWhile | 如果Stream是有序的(Ordered),那么返回从首个元素开始的最长命中序列(符合传入的Predicate的最长命中序列)组成的Stream;如果是无序的,那么返回的是在遇到不匹配元素前,所有匹配符合传入的Predicate的元素序列组成的Stream。 代码实例 --------------------------------------------------------------------------------------------------------- 对于有序串行列表 Stream of = Stream.of(“1,2,3,4,5,6,7,8,9,10”.split(",")); System.out.println(of.isParallel()); of.takeWhile(ele -> Integer.valueOf(ele)>3).forEach(System.out::println); //上述代码无法无法输出任何东西,因为开头不匹配 --------------------------------------------------------------------------------------------------------- of.parallel(). unordered(). takeWhile(ele -> Integer.valueOf(ele)>2).forEach(System.out::println); 上述代码可以返回多种结果,完全看运气,不要用,除非你是用来抽奖的。不确定的原因是,流是无序的,而takeWhile会在遇到不匹配条件的元素终止操作,从而每次操作都是一个新的惊喜。 |
| dropWhile | 与takeWhile相反,匹配了就删掉这个元素,而后返回未被删除的元素流,同样会在不匹配的时候停留下来,同样对于有序流结果是确定的,对于无序流结果是不确定的。 |
Stream的常见末端方法
-
forEach(Consumer action):遍历流,并处理数据
forEachOrder(Consumer action);遍历流,并处理数据
//Stream.generate(Supplier s).limit(100).forEach(System.out::println); -
toArray():将流转为Object类型数组
//Object[] array = of.toArray();此处不可强转
toArray(IntFunction<A[]>):生成一个指定数组容器用于接收流中元素
//of.toArray(String[]::new);
推荐使用这个方法,编译器可以自动帮我们生成合适长度的数组存储数据,数据类型由程序员显示指定 -
reduce():合并流中元素,这是一个非常好用而又难用的方法,小心谨慎使用
这个方法有三个重载方法,过于复杂,请看本文的末尾细细拆解 -
min(Comparator c):返回一个最小值Optiopnal实例
of.min((e1, e2) -> {
Integer ee1 = Integer.valueOf(e1);
Integer ee2 = Integer.valueOf(e2);
return ee1>ee2? 1:(ee1==ee2? 0:-1);
}); -
max(Comparator c):返回流中最大值Optiopnal实例
参考min()方法 -
count():返回流中元素数量
返回long类型整数
-
anyMatch(Predicate p):判断流中是否至少有一个满足了条件
boolean b = of.anyMatch(ele -> ele.length()>2); -
allMatch(Predicate p):判断流中所有元素是否都满足了条件
boolean b = of.allMatch(ele -> ele.length()>2);
-
noneMatch(Predicate p):判断是否流中所有元素都不满足条件
boolean b = of.noneMatch(ele -> ele.length()>2);
-
findFirst():返回一个Optional实例
对于有序流就返回第一个;无序的就抽奖,随便返回。无序有序空流均返回Optional.empty
-
findAny():返回流中的任意一个元素构成的Optional实例
这就是真的抽奖,随便返回一个
Stream的收集(转为其他形式存储)
| 方法名 | 实例代码及说明 |
|---|---|
| collect(Collector<? super T,A,R> collector) | List asList = stringStream.collect(Collectors.toList()); |
| collect(Supplier supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner) | List asList = stringStream.collect(ArrayList::new, ArrayList::add, ArrayList::addAll) |
这里可以看到第二个重载方法是可以自定义化的,你可以指定任意容器去接受最终结果,通过你可以指定积累操作,还有混合操作,其实第一个重载方法已经提供很多我们需要的功能,这些功能都在Collectors工具类里面了,可以去记忆一些相关方法。如(Collectors.toList/toSet/groupingBY……)先挖坑以后再填,逛API就是这样,看一个类学习N个辅助类
reduce的三个重载方法及用法
说明
reduce其实是一个对流进行一定操作后返回一个数据的方法的统称,比如求最大,最小 ,平均值等等均可以看作是一个特殊的reduce方法,以下三个方法只要你脑洞打开就可以进行更加灵活的操作。
reduce(T identity,BinaryOperator accumulator);
//官方实例代码
Integer sum = integers.reduce(0, Integer::sum);
Integer sum = integers.reduce(0, (a, b) -> a+b);
//其他代码实例
Integer mul = integers.reduce(1, (a, b) -> a*b);
Integer max = integers.reduce(0, (a, b) -> a>b?a:b);
/*
形参列表中的identity作为一个结果接收器,参与后续计算,可以将a理解为identity,b理解为流中元素
如官方实例代码中表示将流中元素挨个相加,保存于identity上,即identity+=流中元素
其他代码实例1表示将流中元素挨个相乘,保存于identity上,即identity*=流中元素
其他代码实例2表示获取流中最大元素,每次获取一个元素和identity相比而后较大者保存于其中
*/
reduce(BinaryOperator<T> accumulator);
//这个是上一个方法的孪生方法,大同小异,区别就在于这个将结果保存于Optional中
Optional<Integer> reduce = of.reduce((a, b) -> a>b?a:b);
System.out.println(reduce);
reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator combiner);
//这是一个最骚的方法,先对数据进行操作,而后整合数据,有种Comparator.comparing()方法的味道
//该方法必须使用==并行流==来进行操作,不然无法得到想要的操作
Integer reduce = Stream.of(1,2,3).parallel().reduce(1, (a, b) -> a*b*3, (a,b)-> a+b);//18
//以上代码意思为每个元素先放大三倍,而后将放大的结果依次相加
/*
跟之前的两个重载方法其实是同根的可以理解为两步走
第一步得到新的流:(a, b) -> a*b*3
identity * 流中元素 * 3 ---->{1*1*3,1*2*3,1*3*3}---->得到新的流 {3,6,9}
第二步比较特殊的一个地方是,由于没有现存的identity值了,需要将上一步骤得到的新流的第一个值作为 identity的值,所以第一个元素不执行lambda表达式,(a,b)-> a+b)
identity += 流中元素 ---->3+6+9---->返回18
*/
//----------------------------------------------------------------------------------------
/*当不使用并行流来进行操作的时候得不到想要的数据*/
Integer reduce1 = Stream.of(1,2,3).reduce(1, (a, b) -> a*b*3, (a,b)-> a+b+0);//162
/*
推测上述操作的过程为 (1*1*3) * (1*2*3) * (1*3*3)=3*6*9=162
本质上就和前述的两个方法一摸一样,目前没有能力研读源码不知为何会变成这样,日后再议
*/
小结
- Stream对数据的操作更灵活(过滤,排序,缩放等等操作),可以先使用流完成对数据的操作,再将数据转成数组/集合
- Stream中有对空元素友好的操作方法
- Stream只允许操作引用类型数据,如果涉及到基本数据类型的操作,
请使用对应IntStream LongStream DoubleStream 这三个流进行操作 - 使用reduce的时候记得可以用并行流操作加快速度,注意不要搞错了
- 使用数组进行构造流的时候,如果一次性传入多个数组,则会形成一个数组流,流中保存的是数组,不是数组中的元素,遍历的时候返回的是数组的地址。但是如果只是传入一个数组,则生成数据流,遍历的时候可以打印相关数据
本文深入讲解Java Stream API的使用方法,包括流的创建、中间操作、终端操作以及收集方法。探讨了filter、map、reduce等核心方法的应用场景,以及如何利用Stream进行数据处理和转换。
944

被折叠的 条评论
为什么被折叠?



