Java8新特性之Steam流式编程

Java8新特性之Steam流式编程

2023年7月21日发(作者:)

Java8新特性之Steam流式编程特地感谢鲁班⼤叔的分享,原学习地址:以下是学习过程整理的笔记1、简介Stream 流处理,⾸先要澄清的是 java8 中的 Stream 与 I/O 流 InputStream 和 OutputStream 是完全不同的概念。Stream 机制是针对集合迭代器的增强。流允许你⽤声明式的⽅式处理数据集合(通过查询语句来表达,⽽不是临时编写⼀个实现)2、创建对象流的三种⽅式1. 由集合对象创建流。对⽀持流处理的对象调⽤ stream()。⽀持流处理的对象包括 Collection 集合及其⼦类List list = (1,2,3);Stream stream = ();2. 由数组创建流。通过静态⽅法 Arrays.*stream()* 将数组转化为流(Stream)IntStream stream = (new int[]{3, 2, 1});3. 通过静态⽅法 () ,但是底层其实还是调⽤ ()Stream stream = (1, 2, 3);注意:还有两种⽐较特殊的流空流:()⽆限流:**te() ** 和 **e() **。可以配合 limit() 使⽤可以限制⼀下数量// 接受⼀个 Supplier 作为参数te(Math::random).limit(10).forEach(::println);// 初始值是 0,新值是前⼀个元素值 + e(0, n -> n + 2).limit(10).forEach(::println);3、流处理的特性1. 不存储数据2. 不会改变数据源3. 不可以重复使⽤测试⽤例:package es;import ;import ;import ist;import ;import tors;import ;/** * 流特性 * * @author godfrey * @since 2021-08-15 */class StreamFeaturesTest { /** * 流的简单例⼦ */ @Test public void test1() { List list = (1, 2, 5, 9, 7, 3).filter(val -> val > 2).sorted().collect(()); for (Integer item : list) { n(item); } } /** * 流不会改变数据源 */ @Test public void test2() { List list = new ArrayList<>(); (1); (2); (3); (1); Equals(3, ().distinct().count()); Equals(4, ()); } /** * 流不可以重复使⽤ */ @Test(expected = ) public void test3() { Stream integerStream = (1, 2, 3); Stream newStream = (val -> val > 2); (1); }}⾸先,test1() 向我们展⽰了流的⼀般⽤法,由下图可见,源数据流经管道,最后输出结果数据。然后,我们先看 test3(),源数组产⽣的流对象 integerStream 在调⽤ filter() 之后,数据⽴即流向了 newStream。 正因为流“不保存数据”的特性,所以重复利⽤integerStream 再次调⽤ skip(1) ⽅法,会抛出⼀个 *IllegalStateException* 的异常:lStateException: stream has already been operated upon or closed所以说流不存储数据,且流不可以重复使⽤。最后,我们来看 test2(),尽管我们对 list 对象⽣成的流 () 做了去重操作 distinct() ,但是并不影响源数据对象 list。4、流处理的操作类型Stream 的所有操作连起来组合成了管道,管道有两种操作:第⼀种,中间操作(intermediate)。调⽤中间操作⽅法返回的是⼀个新的流对象第⼆种,终值操作(terminal)。在调⽤该⽅法后,将执⾏之前所有的中间操作,并返回结果5、流处理的执⾏顺序为了更好地演⽰效果,我们⾸先要了解⼀下 () ⽅法, 这个⽅法和 h() 使⽤⽅法类似,都接受 Consumer 作为参数流操作⽅法流操作类型peek()forEach()中间操作终值操作所以,我们可以⽤ peek 来证明流的执⾏顺序。我们定义⼀个 Apple 对象:package ;/** * @author godfrey * @since 2021-08-15 */public class Apple { /** * 编号 */ private int id; /** * 颜⾊ */ private String color; /** * 重量 */ private int weight; /** * 产地 */ private String birthplace; public Apple(int id, String color, int weight, String birthplace) { = id; = color; = weight; lace = birthplace; } //Setter、Getter省略}然后创建多个苹果放到 appleStore 中public class StreamTest { private static final List appleStore = ( new Apple(1, "red", 500, "湖南"), new Apple(2, "red", 100, "天津"), new Apple(3, "green", 300, "湖南"), new Apple(4, "green", 200, "天津"), new Apple(5, "green", 100, "湖南") ); public static void main(String[] args) { ().filter(apple -> ght() > 100) .peek(apple -> n("通过第1层筛选 " + apple)) .filter(apple -> "green".equals(or())) .peek(apple -> n("通过第2层筛选 " + apple)) .filter(apple -> "湖南".equals(thplace())) .peek(apple -> n("通过第3层筛选 " + apple)) .collect(()); }}测试结果如下:以上测试例⼦的执⾏顺序⽰意图:总之,执⾏顺序会⾛⼀个“之”字形注意:如果我们注释掉 .collect(()), 我们会发现⼀⾏语句也不会打印出来。 这刚好证明了:通过连续执⾏多个操作倒便就组成了 Stream 中的执⾏管道(pipeline)。需要注意的是这些管道被添加后并不会真正执⾏,只有等到调⽤终值操作之后才会执⾏。6、⽤流收集数据与 SQL 统计函数Collector 被指定和四个函数⼀起⼯作,并实现累加 entries 到⼀个可变的结果容器,并可选择执⾏该结果的最终变换。 这四个函数就是:接⼝函数supplier()combiner()finisher()作⽤返回值BiConsumerBinaryOperatorFunction创建并返回⼀个新的可变结果容器Supplier将两个结果容器组合成⼀个转换中间结果为终值结果accumulator()把输⼊值加⼊到可变结果容器Collectors 则是重要的⼯具类,提供给我⼀些 Collector 实现。 Stream 接⼝中 collect() 就是使⽤ Collector 做参数的。 其中,collect(Supplier supplier,BiConsumer accumulator, BiConsumer combiner) ⽆⾮就是⽐ Collector 少⼀个 finisher,本质上是⼀样的!遍历在传统的 javaEE 项⽬中数据源⽐较单⼀⽽且集中,像这类的需求都我们可能通过关系数据库中进⾏获取计算。现在的互联⽹项⽬数据源成多样化有:关系数据库、NoSQL、Redis、mongodb、ElasticSearch、Cloud Server 等。这时就需我们从各数据源中汇聚数据并进⾏统计。Stream + Lambda的组合就是为了让 Java 语句更像查询语句,取代繁杂的 for 循环。CREATE TABLE `applestore` ( `id` INT NOT NULL AUTO_INCREMENT COMMENT '编号', `color` VARCHAR (50) COMMENT '颜⾊', `weight` INT COMMENT '重量', `birthplace` VARCHAR (50) COMMENT '产地', PRIMARY KEY (`id`)) COMMENT = '⽔果商店';另外还有数据初始化语句INSERT INTO applestore VALUES (1, "red", 500,"湖南");INSERT INTO applestore VALUES (2, "red", 100,"湖南");INSERT INTO applestore VALUES (3, "green", 300, "湖南");INSERT INTO applestore VALUES (4, "green", 200, "天津");INSERT INTO applestore VALUES (5, "green", 100, "湖南");测试⽤例:public class StreamStatisticsTest {

List appleStore;

@Before public void initData() { appleStore = ( new Apple(1, "red", 500, "湖南"), new Apple(2, "red", 100, "天津"), new Apple(3, "green", 300, "湖南"), new Apple(4, "green", 200, "天津"), new Apple(5, "green", 100, "湖南") ); } @Test public void test1() { Integer weight1 = ().collect(gInt(apple -> ght())); n(weight1); Integer weight2 = ().collect(gInt(Apple::getWeight)); n(weight2); }}6.1、求和gInt()gLong()gDouble()通过引⽤

import static gInt 就可以直接调⽤ summingInt() Apple::getWeight() 可以写为 apple -> ght(),求和函数的参数是结果转换函数 Function6.2、求平均值ingInt()ingKLong()ingDouble()6.3、归约ng()@Testpublic void reduce() { Integer sum = ().collect(reducing(0, Apple::getWeight, (a, b) -> a + b)); n(sum);}归约就是为了遍历数据容器,将每个元素对象转换为特定的值,通过累积函数,得到⼀个最终值。转换函数,函数输⼊参数的对象类型是跟 Stream 中的 T ⼀样的对象类型,输出的对象类型的是和初始值⼀样的对象类型累积函数,就是把转换函数的结果与上⼀次累积的结果进⾏⼀次合并,如果是第⼀次累积,那么取初始值来计算累积函数还可以作⽤于两个 Stream 合并时的累积,这个可以结合 groupingBy 来理解初始值的对象类型,和每⼀次累积函数输出值的对象类型是相同的,这样才能⼀直进⾏累积函数的运算。归约不仅仅可以⽀持加法,还可以⽀持⽐如乘法以及其他更⾼级的累积公式。计数只是归约的⼀种特殊形式ng(): 初始值为 0,转换函数 f(x)=1(x 就是 Stream 的 T 类型),累积函数就是“做加法”6.4、分组ngBy()分组就和 SQL 中的 GROUP BY ⼗分类似,所以 groupingBy() 的所有参数中有⼀个参数是 Collector接⼝,这样就能够和 求和/求平均值/归约 ⼀起使⽤。传⼊参数的接⼝是 Function 接⼝,实现这个接⼝可以是实现从 A 类型到 B 类型的转换其中有⼀个⽅法可以传⼊参数

Supplier mapFactory,这个可以通过⾃定义 Map⼯⼚,来创建⾃定义的分组 Map分区只是分组的⼀种特殊形式ioningBy() 传⼊参数的是 Predicate 接⼝,分区相当于把流中的数据,分组分成了“正反两个阵营”7、数值流我们之前在求和时⽤到的例⼦,().collect(summingInt(Apple::getWeight)),我就被 IDEA 提醒:().collect(summingInt(Apple::getWeight))The 'collect(summingInt())' can be replaced with 'mapToInt().sum()'这就告诉我们可以先转化为数值流,然后再⽤ IntStream 做求和。Java8引⼊了三个原始类型特化流接⼝:IntStream,LongStream,DoubleStream,分别将流中的元素特化为 int,long,double。普通对象流和原始类型特化流之间可以相互转化其中 IntStream 和 LongStream 可以调⽤ asDoubleStream 变为 DoubleStream,但是这是单向的转化⽅法。IntStream#boxed() 可以得到 Stream ,这个也是⼀个单向⽅法,⽀持数值流转换回对象流,LongStream 和 DoubleStream 也有类似的⽅法。7.1、⽣成⼀个数值流(int startInclusive, int endExclusive)losed(int startInclusive, int endInclusive)range 和 rangeClosed 的区别在于数值流是否包含 end 这个值。range 代表的区间是 [start, end) , rangeClosed 代表的区间是 [start, end]LongStream 也有 range 和 rangeClosed ⽅法,但是 DoubleStream 没有!7.2、p 就是流中的每个对象,转换产⽣⼀个对象流。pToInt 指定流中的每个对象,转换产⽣⼀个 IntStream 数值流;类似的,还有 flatMapToLong,p 数值流中的每个对象,转换产⽣⼀个数值流flatMap 可以代替⼀些嵌套循环来开展业务:⽐如我们要求勾股数(即 aa+bb=c*c 的⼀组数中的 a,b,c),且我们要求 a 和 b 的范围是 [1,100],我们在 Java8之前会这样写:@Testpublic void testJava() { List resultList = new ArrayList<>(); for (int a = 1; a <= 100; a++) { for (int b = a; b <= 100; b++) { double c = (a * a + b * b); if (c % 1 == 0) { (new int[]{a, b, (int) c}); } } } int size = (); for (int i = 0; i < size && i < 5; i++) { int[] a = (i); n(a[0] + " " + a[1] + " " + a[2]); }}

Java8之后,我们可以⽤上 flatMap:@Testpublic void flatMap() { Stream stream = losed(1, 100) .boxed() .flatMap(a -> losed(a, 100) .filter(b -> (a * a + b * b) % 1 == 0) .mapToObj(b -> new int[]{a, b, (int) (a * a + b * b)}) ); (5).forEach(a -> n(a[0] + " " + a[1] + " " + a[2]));}创建⼀个从 1 到 100 的数值范围来创建 a 的值。对每个给定的 a 值,创建⼀个三元数流。flatMap ⽅法在做映射的同时,还会把所有⽣成的三元数流扁平化成⼀个流。总结Stream 主要包括对象流和数值流两⼤类() , () , () ,te() , e() ⽅法创建对象流()和 losed() 可以创建数值流,对象流和数值流可以相互转换Collector 收集器接⼝,可以实现归约,统计函数(求和,求平均值,最⼤值,最⼩值),分组等功能流的执⾏,需要调⽤终值操作。流中每个元素执⾏到不能继续执⾏下去,才会转到另⼀个元素执⾏。⽽不是分阶段迭代数据容器中的所有元素!flatMap 可以给流中的每个元素⽣成⼀个对应的流,并且扁平化为⼀个流

发布者:admin,转转请注明出处:http://www.yc00.com/web/1689899012a293260.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信