最全延迟执行与不可变,系统讲解JavaStream数据处理(1),当上项目经理才知道

最后

这份清华大牛整理的进大厂必备的redis视频、面试题和技术文档

祝大家早日进入大厂,拿到满意的薪资和职级~~~加油!!

感谢大家的支持!!

image.png

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

以上都是我们让我们易于理解的创建方式,还有一种方式可以创建一个无限制元素数量的Stream——generate():

public static Stream generate(Supplier<? extends T> s) {

Objects.requireNonNull(s);

return StreamSupport.stream(

new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);

}

复制代码

从方法参数上来看,它接受一个函数式接口——Supplier作为参数,这个函数式接口是用来创建对象的接口,你可以将其类比为对象的创建工厂,Stream将从此工厂中创建的对象放入Stream中:

Stream generate = Stream.generate(() -> “Supplier”);

Stream generateInteger = Stream.generate(() -> 123);

复制代码

我这里是为了方便直接使用Lamdba构造了一个Supplier对象,你也可以直接传入一个Supplier对象,它会通过Supplier接口的get() 方法来构造对象。

2.2 通过集合类库进行创建

相较于上面一种来说,第二种方式更较为常用,我们常常对集合就行Stream流操作而非手动构建一个Stream:

Stream integerStreamList = List.of(1, 2, 3).stream();

Stream stringStreamList = List.of(“1”, “2”, “3”).stream();

复制代码

在Java8中,集合的顶层接口Collection被加入了一个新的接口默认方法——stream(),通过这个方法我们可以方便的对所有集合子类进行创建Stream的操作:

Stream listStream = List.of(1, 2, 3).stream();

Stream setStream = Set.of(1, 2, 3).stream();

复制代码

通过查阅源码,可以发先 stream() 方法本质上还是通过调用一个Stream工具类来创建Stream:

default Stream stream() {

return StreamSupport.stream(spliterator(), false);

}

复制代码

2.3 创建并行流

在以上的示例中所有的Stream都是串行流,在某些场景下,为了最大化压榨多核CPU的性能,我们可以使用并行流,它通过JDK7中引入的fork/join框架来执行并行操作,我们可以通过如下方式创建并行流:

Stream integerParallelStream = Stream.of(1, 2, 3).parallel();

Stream stringParallelStream = Stream.of(“1”, “2”, “3”).parallel();

Stream integerParallelStreamList = List.of(1, 2, 3).parallelStream();

Stream stringParallelStreamList = List.of(“1”, “2”, “3”).parallelStream();

复制代码

是的,在Stream的静态方法中没有直接创建并行流的方法,我们需要在构造Stream后再调用一次parallel()方法才能创建并行流,因为调用parallel()方法并不会重新创建一个并行流对象,而是在原有的Stream对象上面设置了一个并行参数。

当然,我们还可以看到,Collection接口中可以直接创建并行流,只需要调用与stream() 对应的parallelStream()方法,就像我刚才讲到的,他们之间其实只有参数的不同:

default Stream stream() {

return StreamSupport.stream(spliterator(), false);

}

default Stream parallelStream() {

return StreamSupport.stream(spliterator(), true);

}

复制代码

不过一般情况下我们并不需要用到并行流,在Stream中元素不过千的情况下性能并不会有太大提升,因为将元素分散到不同的CPU进行计算也是有成本的。

并行的好处是充分利用多核CPU的性能,但是使用中往往要对数据进行分割,然后分散到各个CPU上去处理,如果我们使用的数据是数组结构则可以很轻易的进行分割,但是如果是链表结构的数据或者Hash结构的数据则分割起来很明显不如数组结构方便。

所以只有当Stream中元素过万甚至更大时,选用并行流才能带给你更明显的性能提升。

最后,当你有一个并行流的时候,你也可以通过sequential() 将其方便的转换成串行流:

Stream.of(1, 2, 3).parallel().sequential();

复制代码

2.4 连接Stream

如果你在两处构造了两个Stream,在使用的时候希望组合在一起使用,可以使用concat():

Stream concat = Stream

.concat(Stream.of(1, 2, 3), Stream.of(4, 5, 6));

复制代码

如果是两种不同的泛型流进行组合,自动推断会自动的推断出两种类型相同的父类:

Stream integerStream = Stream.of(1, 2, 3);

Stream stringStream = Stream.of(“1”, “2”, “3”);

Stream<? extends Serializable> stream = Stream.concat(integerStream, stringStream);

复制代码

3. Stream转换操作之无状态方法


无状态方法:即此方法的执行无需依赖前面方法执行的结果集。

在Stream中无状态的API我们常用的大概有以下三个:

  1. map()方法:此方法的参数是一个Function对象,它可以使你对集合中的元素做自定义操作,并保留操作后的元素。

  2. filter()方法:此方法的参数是一个Predicate对象,Predicate的执行结果是一个Boolean类型,所以此方法只保留返回值为true的元素,正如其名我们可以使用此方法做一些筛选操作。

  3. flatMap()方法:此方法和map()方法一样参数是一个Function对象,但是此Function的返回值要求是一个Stream,该方法可以将多个Stream中的元素聚合在一起进行返回。

先来看看一个map()方法的示例:

Stream integerStreamList = List.of(1, 2, 3).stream();

Stream mapStream = integerStreamList.map(i -> i * 10);

复制代码

我们拥有一个List,想要对其中的每个元素进行乘10 的操作,就可以采用如上写法,其中的i是对List中元素的变量名, 后面的逻辑则是要对此元素进行的操作,以一种非常简洁明了的方式传入一段代码逻辑执行,这段代码最后会返回一个包含操作结果的新Stream。

这里为了更好的帮助大家理解,我画了一个简图:


接下来是filter()方法示例:

Stream integerStreamList = List.of(1, 2, 3).stream();

Stream filterStream = integerStreamList.filter(i -> i >= 20);

复制代码

在这段代码中会执行i >= 20 这段逻辑,然后将返回值为true的结果保存在一个新的Stream中并返回。

这里我也有一个简单的图示:


flatMap() 方法的描述在上文我已经描述过,但是有点过于抽象,我在学习此方法中也是搜索了很多示例才有了较好的理解。

根据官方文档的说法,此方法是为了进行一对多元素的平展操作:

List orders = List.of(new Order(), new Order());

Stream itemStream = orders.stream()

.flatMap(order -> order.getItemList().stream());

复制代码

这里我通过一个订单示例来说明此方法,我们的每个订单中都包含了一个商品List,如果我想要将两个订单中所有商品List组成一个新的商品List,就需要用到flatMap()方法。

在上面的代码示例中可以看到每个订单都返回了一个商品List的Stream,我们在本例中只有两个订单,所以也就是最终会返回两个商品List的Stream,flatMap()方法的作用就是将这两个Stream中元素提取出来然后放到一个新的Stream中。

老规矩,放一个简单的图示来说明:

图例中我使用青色代表Stream,在最终的输出中可以看到flatMap()将两个流变成了一个流进行输出,这在某些场景中非常有用,比如我上面的订单例子。


还有一个很不常用的无状态方法peek()

Stream peek(Consumer<? super T> action);

复制代码

peek方法接受一个Consumer对象做参数,这是一个无返回值的参数,我们可以通过peek方法做些打印元素之类的操作:

Stream peekStream = integerStreamList.peek(i -> System.out.println(i));

复制代码

然而如果你不太熟悉的话,不建议使用,某些情况下它并不会生效,比如:

List.of(1, 2, 3).stream()

.map(i -> i * 10)

.peek(System.out::println)

.count();

复制代码

API文档上面也注明了此方法是用于Debug,通过我的经验,只有当Stream最终需要重新生产元素时,peek才会执行。

上面的例子中,count只需要返回元素个数,所以peek没有执行,如果换成collect方法就会执行。

或者如果Stream中存在过滤方法如filter方法和match相关方法,它也会执行。

3.1 基础类型Stream

上一节提到了三个Stream中最常用的三个无状态方法,在Stream的无状态方法中还有几个和map()与flatMap()对应的方法,它们分别是:

  1. mapToInt

  2. mapToLong

  3. mapToDouble

  4. flatMapToInt

  5. flatMapToLong

  6. flatMapToDouble

这六个方法首先从方法名中就可以看出来,它们只是在map()或者flatMap()的基础上对返回值进行转换操作,按理说没必要单拎出来做成一个方法,实际上它们的关键在于返回值:

  1. mapToInt返回值为IntStream

  2. mapToLong返回值为LongStream

  3. mapToDouble返回值为DoubleStream

  4. flatMapToInt返回值为IntStream

  5. flatMapToLong返回值为LongStream

  6. flatMapToDouble返回值为DoubleStream

在JDK5中为了使Java更加的面向对象,引入了包装类的概念,八大基础数据类型都对应着一个包装类,这使你在使用基础类型时可以无感的进行自动拆箱/装箱,也就是自动使用包装类的转换方法。

比如,在最前文的示例中,我用了这样一个例子:

Stream integerStream = Stream.of(1, 2, 3);

复制代码

我在创建Stream中使用了基本数据类型参数,其泛型则被自动包装成了Integer,但是我们有时可能忽略自动拆装箱也是有代价的,如果我们想在使用Stream中忽略这个代价则可以使用Stream中转为基础数据类型设计的Stream:

  1. IntStream:对应 基础数据类型中的int、short、char、boolean

  2. LongStream:对应基础数据类型中的long

  3. DoubleStream:对应基础数据类型中的double和float

在这些接口中都可以和上文的例子一样通过of方法构造Stream,且不会自动拆装箱。

所以上文中提到的那六个方法实际上就是将普通流转换成这种基础类型流,在我们需要的时候可以拥有更高的效率。

基础类型流在API方面拥有Stream一样的API,所以在使用方面只要明白了Stream,基础类型流也都是一样的。

:IntStream、LongStream和DoubleStream都是接口,但并非继承自Stream接口。

3.2 无状态方法的循环合并

说完无状态的这几个方法我们来看一个前文中的例子:

List list = List.of(1, 2, 3).stream()

.filter(i -> i > 2)

.filter(i -> i < 10)

.filter(i -> i % 2 == 0)

.collect(toList());

复制代码

在这个例子中我用了三次filter方法,那么大家觉得Stream会循环三次进行过滤吗?

如果换掉其中一个filter为map,大家觉得会循环几次?

List list = List.of(1, 2, 3).stream()

.map(i -> i * 10)

.filter(i -> i < 10)

.filter(i -> i % 2 == 0)

.collect(toList());

复制代码

从我们的直觉来看,需要先使用map方法对所有元素做处理,然后再使用filter方法做过滤,所以需要执行三次循环。

但回顾无状态方法的定义,你可以发现其他这三个条件可以放在一个循环里面做,因为filter只依赖map的计算结果,而不必依赖map执行完后的结果集,所以只要保证先操作map再操作filter,它们就可以在一次循环内完成,这种优化方式被称为循环合并

所有的无状态方法都可以放在同一个循环内执行,它们也可以方便的使用并行流在多个CPU上执行。

4. Stream转换操作之有状态方法


前面说完了无状态方法,有状态方法就比较简单了,只看名字就可以知道它的作用:

| 方法名 | 方法结果 |

| — | — |

| distinct() | 元素去重。 |

| sorted() | 元素排序,重载的两个方法,需要的时候可以传入一个排序对象。 |

| limit(long maxSize) | 传入一个数字,代表只取前X个元素。 |

| skip(long n) | 传入一个数字,代表跳过X个元素,取后面的元素。 |

| takeWhile(Predicate predicate) | JDK9新增,传入一个断言参数当第一次断言为false时停止,返回前面断言为true的元素。 |

| dropWhile(Predicate predicate) | JDK9新增,传入一个断言参数当第一次断言为false时停止,删除前面断言为true的元素。 |

以上就是所有的有状态方法,它们的方法执行都必须依赖前面方法执行的结果集才能执行,比如排序方法就需要依赖前面方法的结果集才能进行排序。

写在最后

很多人感叹“学习无用”,实际上之所以产生无用论,是因为自己想要的与自己所学的匹配不上,这也就意味着自己学得远远不够。无论是学习还是工作,都应该有主动性,所以如果拥有大厂梦,那么就要自己努力去实现它。

最后祝愿各位身体健康,顺利拿到心仪的offer!

由于文章的篇幅有限,所以这次的蚂蚁金服和京东面试题答案整理在了PDF文档里

蚂蚁、京东Java岗4面:原理+索引+底层+分布式+优化等,已拿offer

蚂蚁、京东Java岗4面:原理+索引+底层+分布式+优化等,已拿offer

蚂蚁、京东Java岗4面:原理+索引+底层+分布式+优化等,已拿offer

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

edicate predicate) | JDK9新增,传入一个断言参数当第一次断言为false时停止,删除前面断言为true的元素。 |

以上就是所有的有状态方法,它们的方法执行都必须依赖前面方法执行的结果集才能执行,比如排序方法就需要依赖前面方法的结果集才能进行排序。

写在最后

很多人感叹“学习无用”,实际上之所以产生无用论,是因为自己想要的与自己所学的匹配不上,这也就意味着自己学得远远不够。无论是学习还是工作,都应该有主动性,所以如果拥有大厂梦,那么就要自己努力去实现它。

最后祝愿各位身体健康,顺利拿到心仪的offer!

由于文章的篇幅有限,所以这次的蚂蚁金服和京东面试题答案整理在了PDF文档里

[外链图片转存中…(img-qLk3jphd-1715602394091)]

[外链图片转存中…(img-ay1rxsmS-1715602394092)]

[外链图片转存中…(img-u6bwjeyz-1715602394092)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 19
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值