Stream流的学习与使用

本文深入讲解Java Stream API的使用方法,包括流的创建、中间操作、终端操作以及收集方法。探讨了filter、map、reduce等核心方法的应用场景,以及如何利用Stream进行数据处理和转换。

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);

流类方法的四大类型

  1. 中间方法:对流数据进行操作后重新返回一个流数据,这是一个延迟方法,不会立即执行,只有后续继续调用末端方法才会开始执行。
  2. 终端方法:只可使用一次,使用后流作废,返回一个数据
  3. 有状态的方法:该方法给流增加新属性,如元素的唯一性、元素的最大数量,资源占用大
  4. 短路方法:短路方法可以尽早结束对流的操作,不必检查所有的元素

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的常见末端方法

  1. forEach(Consumer action):遍历流,并处理数据

    forEachOrder(Consumer action);遍历流,并处理数据
    //Stream.generate(Supplier s).limit(100).forEach(System.out::println);

  2. toArray():将流转为Object类型数组
    //Object[] array = of.toArray();此处不可强转
    toArray(IntFunction<A[]>):生成一个指定数组容器用于接收流中元素
    //of.toArray(String[]::new);
    推荐使用这个方法,编译器可以自动帮我们生成合适长度的数组存储数据,数据类型由程序员显示指定

  3. reduce():合并流中元素,这是一个非常好用而又难用的方法,小心谨慎使用
    这个方法有三个重载方法,过于复杂,请看本文的末尾细细拆解

  4. 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);
    });

  5. max(Comparator c):返回流中最大值Optiopnal实例
    参考min()方法

  6. count():返回流中元素数量

    返回long类型整数

  7. anyMatch(Predicate p):判断流中是否至少有一个满足了条件
    boolean b = of.anyMatch(ele -> ele.length()>2);

  8. allMatch(Predicate p):判断流中所有元素是否都满足了条件

    boolean b = of.allMatch(ele -> ele.length()>2);

  9. noneMatch(Predicate p):判断是否流中所有元素都不满足条件

    boolean b = of.noneMatch(ele -> ele.length()>2);

  10. findFirst():返回一个Optional实例

    对于有序流就返回第一个;无序的就抽奖,随便返回。无序有序空流均返回Optional.empty

  11. 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
	本质上就和前述的两个方法一摸一样,目前没有能力研读源码不知为何会变成这样,日后再议
*/

小结

  1. Stream对数据的操作更灵活(过滤,排序,缩放等等操作),可以先使用流完成对数据的操作,再将数据转成数组/集合
  2. Stream中有对空元素友好的操作方法
  3. Stream只允许操作引用类型数据,如果涉及到基本数据类型的操作,
    请使用对应IntStream LongStream DoubleStream 这三个流进行操作
  4. 使用reduce的时候记得可以用并行流操作加快速度,注意不要搞错了
  5. 使用数组进行构造流的时候,如果一次性传入多个数组,则会形成一个数组流,流中保存的是数组,不是数组中的元素,遍历的时候返回的是数组的地址。但是如果只是传入一个数组,则生成数据流,遍历的时候可以打印相关数据
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值