归约、分组与分区,深入讲解JavaStream终结操作,linux软件工程师面试题

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

如果你的初始值是1,则在并发情况下每个线程的初始化都是1,那么你的最终和就会比你预想的结果要大。

2.2 max:利用归约求最大

max方法也是一个归约方法,它是直接调用了reduce方法。

先来看一个示例:

Optional max = List.of(1, 2, 3).stream()

.max((a, b) -> {

if (a > b) {

return 1;

} else {

return -1;

}

});

复制代码

没错,这就是max方法用法,这让我觉得我不是在使用函数式接口,当然你也可以使用Integer的方法进行简化:

Optional max = List.of(1, 2, 3).stream()

.max(Integer::compare);

复制代码

哪怕如此,这个方法依旧让我感觉到很繁琐,我虽然可以理解在max方法里面传参数是为了让我们自己自定义排序规则,但我不理解为什么没有一个默认按照自然排序进行排序的方法,而是非要让我传参数。

直到后来我想到了基础类型Stream,果然,它们里面是可以无需传参直接拿到最大值:

OptionalLong max = LongStream.of(1, 2, 3).max();

复制代码

果然,我能想到的,类库设计者都想到了~

:OptionalLong是Optional对基础类型long的封装。

2.3 min:利用归约求最小

min还是直接看例子吧:

Optional max = List.of(1, 2, 3).stream()

.min(Integer::compare);

复制代码

它和max区别就是底层把 > 换成了 <,过于简单,不再赘述。

3. 收集器


第三节我们来看看收集器,它的作用是对Stream中的元素进行收集而形成一个新的集合。

虽然我在本篇开头的时候已经给过一张思维导图了,但是由于收集器的API比较多所以我又画了一张,算是对开头那张的补充:

收集器的方法名是collect,它的方法定义如下:

<R, A> R collect(Collector<? super T, A, R> collector);

复制代码

顾名思义,收集器是用来收集Stream的元素的,最后收集成什么我们可以自定义,但是我们一般不需要自己写,因为JDK内置了一个Collector的实现类——Collectors。

3.1 收集方法

通过Collectors我们可以利用它的内置方法很方便的进行数据收集:

比如你想把元素收集成集合,那么你可以使用toCollection或者toList方法,不过我们一般不使用toCollection,因为它需要传参数,没人喜欢传参数。

你也可以使用toUnmodifiableList,它和toList区别就是它返回的集合不可以改变元素,比如删除或者新增。

再比如你要把元素去重之后收集起来,那么你可以使用toSet或者toUnmodifiableSet。

接下来放一个比较简单的例子:

// toList

List.of(1, 2, 3).stream().collect(Collectors.toList());

// toUnmodifiableList

List.of(1, 2, 3).stream().collect(Collectors.toUnmodifiableList());

// toSet

List.of(1, 2, 3).stream().collect(Collectors.toSet());

// toUnmodifiableSet

List.of(1, 2, 3).stream().collect(Collectors.toUnmodifiableSet());

复制代码

以上这些方法都没有参数,拿来即用,toList底层也是经典的ArrayList,toSet 底层则是经典的HashSet。


也许有时候你也许想要一个收集成一个Map,比如通过将订单数据转成一个订单号对应一个订单,那么你可以使用toMap():

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

Map<String, Order> map = orders.stream()

.collect(Collectors.toMap(Order::getOrderNo, order -> order));

复制代码

toMap() 具有两个参数:

  1. 第一个参数代表key,它表示你要设置一个Map的key,我这里指定的是元素中的orderNo。

  2. 第二个参数代表value,它表示你要设置一个Map的value,我这里直接把元素本身当作值,所以结果是一个Map<String, Order>。

你也可以将元素的属性当作值:

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

Map<String, List> map = orders.stream()

.collect(Collectors.toMap(Order::getOrderNo, Order::getItemList));

复制代码

这样返回的就是一个订单号+商品列表的Map了。

toMap() 还有两个伴生方法:

  • toUnmodifiableMap():返回一个不可修改的Map。

  • toConcurrentMap():返回一个线程安全的Map。

这两个方法和toMap() 的参数一模一样,唯一不同的就是底层生成的Map特性不太一样,我们一般使用简简单单的toMap() 就够了,它的底层是我们最常用的HashMap() 实现。

toMap() 功能虽然强大也很常用,但是它却有一个致命缺点。

我们知道HahsMap遇到相同的key会进行覆盖操作,但是toMap() 方法生成Map时如果你指定的key出现了重复,那么它会直接抛出异常。

比如上面的订单例子中,我们假设两个订单的订单号一样,但是你又将订单号指定了为key,那么该方法会直接抛出一个IllegalStateException,因为它不允许元素中的key是相同的。

3.2 分组方法

如果你想对数据进行分类,但是你指定的key是可以重复的,那么你应该使用groupingBy 而不是toMap。

举个简单的例子,我想对一个订单集合以订单类型进行分组,那么可以这样:

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

Map<Integer, List> collect = orders.stream()

.collect(Collectors.groupingBy(Order::getOrderType));

复制代码

直接指定用于分组的元素属性,它就会自动按照此属性进行分组,并将分组的结果收集为一个List。

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

Map<Integer, Set> collect = orders.stream()

.collect(Collectors.groupingBy(Order::getOrderType, toSet()));

复制代码

groupingBy还提供了一个重载,让你可以自定义收集器类型,所以它的第二个参数是一个Collector收集器对象。

对于Collector类型,我们一般还是使用Collectors类,这里由于我们前面已经使用了Collectors,所以这里不必声明直接传入一个toSet()方法,代表我们将分组后的元素收集为Set。

groupingBy还有一个相似的方法叫做groupingByConcurrent(),这个方法可以在并行时提高分组效率,但是它是不保证顺序的,这里就不展开讲了。

3.3 分区方法

接下来我将介绍分组的另一种情况——分区,名字有点绕,但意思很简单:

将数据按照TRUE或者FALSE进行分组就叫做分区。

举个例子,我们将一个订单集合按照是否支付进行分组,这就是分区:

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

Map<Boolean, List> collect = orders.stream()

.collect(Collectors.partitioningBy(Order::getIsPaid));

复制代码

因为订单是否支付只具有两种状态:已支付和未支付,这种分组方式我们就叫做分区。

和groupingBy一样,它还具有一个重载方法,用来自定义收集器类型:

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

Map<Boolean, Set> collect = orders.stream()

.collect(Collectors.partitioningBy(Order::getIsPaid, toSet()));

复制代码

3.4 经典复刻方法

终于来到最后一节了,请原谅我给这部分的方法起了一个这么土的名字,但是这些方法确实如我所说:经典复刻。

换言之,就是Collectors把Stream原先的方法又实现了一遍,包括:

  1. mapmapping

  2. filterfiltering

  3. flatMapflatMapping

  4. countcounting

  5. reducereducing

  6. maxmaxBy

  7. **min ** → minBy

这些方法的功能我就不一一列举了,之前的文章已经讲的很详尽了,唯一的不同是某些方法多了一个参数,这个参数就是我们在分组和分区里面讲过的收集参数,你可以指定收集到什么容器内。

我把它们抽出来主要想说的为什么要复刻这么多方法处理,这里我说说个人见解,不代表官方意见。

我觉得主要是为了功能的组合。

什么意思呢?比方说我又有一个需求:使用订单类型对订单进行分组,并找出每组有多少个订单。

订单分组我们已经讲过了,找到其每组有多少订单只要拿到对应list的size就行了,但是我们可以不这么麻烦,而是一步到位,在输出结果的时候键值对就是订单类型和订单数量:

Map<Integer, Long> collect = orders.stream()

.collect(Collectors.groupingBy(Order::getOrderType, counting()));

复制代码

就这样,就这么简单,就好了,这里等于说我们对分组后的数据又进行了一次计数操作。

上面的这个例子可能不对明显,当我们需要对最后收集之后的数据在进行操作时,一般我们需要重新将其转换成Stream然后操作,但是使用Collectors的这些方法就可以让你很方便的在Collectors中进行数据的处理。

总结

无论是哪家公司,都很重视高并发高可用的技术,重视基础,重视JVM。面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,是不是能真的得到锻炼。其实我写了这么多,只是我自己的总结,并不一定适用于所有人,相信经过一些面试,大家都会有这些感触。

最后我整理了一些面试真题资料,技术知识点剖析教程,还有和广大同仁一起交流学习共同进步,还有一些职业经验的分享。

面试了阿里,滴滴,网易,蚂蚁,最终有幸去了网易【面试题分享】

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

总结

无论是哪家公司,都很重视高并发高可用的技术,重视基础,重视JVM。面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,是不是能真的得到锻炼。其实我写了这么多,只是我自己的总结,并不一定适用于所有人,相信经过一些面试,大家都会有这些感触。

最后我整理了一些面试真题资料,技术知识点剖析教程,还有和广大同仁一起交流学习共同进步,还有一些职业经验的分享。

[外链图片转存中…(img-i4kNIx9p-1713704431395)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-E8ntFojv-1713704431395)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值