最新阿里二面:java8的stream api是迭代一次还是迭代多次,最全SpringBoot学习教程

我的面试宝典:一线互联网大厂Java核心面试题库

以下是我个人的一些做法,希望可以给各位提供一些帮助:

整理了很长一段时间,拿来复习面试刷题非常合适,其中包括了Java基础、异常、集合、并发编程、JVM、Spring全家桶、MyBatis、Redis、数据库、中间件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等,且还会持续的更新…可star一下!

image

283页的Java进阶核心pdf文档

Java部分:Java基础,集合,并发,多线程,JVM,设计模式

数据结构算法:Java算法,数据结构

开源框架部分:Spring,MyBatis,MVC,netty,tomcat

分布式部分:架构设计,Redis缓存,Zookeeper,kafka,RabbitMQ,负载均衡等

微服务部分:SpringBoot,SpringCloud,Dubbo,Docker

image

还有源码相关的阅读学习

image

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

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

System.out.println(“以字符a开头的字符串最大长度:” + maxLength);

}

private static boolean isStartWitha(String a){

System.out.println(a + " is start with a:" + a.startsWith(“a”));

return a.startsWith(“a”);

}

private static int length(String a){

System.out.println(“the length of” + a + “:” + a.length());

return a.length();

}

打印结果如下:

abb is start with a:true

the length of abb:3

abcd is start with a:true

the length of abcd:4

fegc is start with a:false

efe is start with a:false

adfes is start with a:true

the length of adfes:5

以字符a开头的字符串最大长度:5

面试官:你确定只是迭代一次吗?有其他情况吗?

:有。filter是一个无状态的中间操作,对于这个中间操作来说,stream处理只需要迭代一次。但是对于有状态的中间操作,就需要迭代多次。

面试官:你刚刚提到有状态的操作和无状态的操作,这个是怎么区分呢?

:在stream api中,无状态的操作是指当前元素的操作不受前面元素的影响,主要包括如下方法:

filter(),flatMap(),flatMapToInt(),flatMapToLong(),flatMapToDouble(),map(),mapToInt(),mapToDouble(),mapToLong(),peek(),unordered()

而有状态的操作是指需要等所有元素处理完之后才能执行当前操作,主要包括下面方法:

distinct(),limit(),skip(),sorted(),sorted()

面试官:有状态的操作,能举个例子吗?

:比如下面这段代码:

public static void main(String[] args) {

List list = Arrays.asList(5, 2, 3, 1, 4);

List newArray = list.stream()

.map(StreamTest2::map1)

.sorted((o1, o2) -> o1 - o2)

.map(StreamTest2::map2)

.collect(Collectors.toList());

System.out.println(“新的有序数组:” + newArray);

}

private static Integer map1(Integer i) {

int result = i * 10;

System.out.println(“线程:” + Thread.currentThread().getName() + " 方法map1入参:" + i + “,输出:” + result);

return result;

}

private static Integer map2(Integer i) {

int result = i * 10;

System.out.println(“线程:” + Thread.currentThread().getName() + " 方法map2入参:" + i + “,输出:” + result);

return result;

}

上面代码中,对原始数组进行了两次迭代,第一次迭代对所有数组元素都调用了map1方法乘以10,然后对新数组进行排序,第二次迭代对排序后的数组元素调用map2方法,即对排序后的数组元素乘以10。方法输出如下:

线程:main 方法map1入参:5,输出:50

线程:main 方法map1入参:2,输出:20

线程:main 方法map1入参:3,输出:30

线程:main 方法map1入参:1,输出:10

线程:main 方法map1入参:4,输出:40

线程:main 方法map2入参:10,输出:100

线程:main 方法map2入参:20,输出:200

线程:main 方法map2入参:30,输出:300

线程:main 方法map2入参:40,输出:400

线程:main 方法map2入参:50,输出:500

新的有序数组:[100, 200, 300, 400, 500]

面试官:了解过底层原理吗?

:我来先画一下Stream的UML类图:

阿里二面:java8的stream api是迭代一次还是迭代多次

这个类图说明以下几点:

  • AbstractPipeline有基本类型的子类,如LongPipeline和DoublePipeline,还有一个引用类型的子类ReferencePipeline。

  • 无论是ReferencePipeline,还是LongPipeline和DoublePipeline等基本类型的Pipeline,都有3个内部类来继承自己。

  • StatelessOp对应无状态的操作,StatefulOp对应有状态的操作,Head对应Collection.stream()方法返回结果。

  • 无论是StatelessOp、StatefulOp还是Head,都是一个Pipeline,这些Pipeline用双向链表串联起来,每个Pipeline节点被看作一个Stage,Head是链表的头结点。上面UML类图中AbstractPipeline类中previousStage和nextStage就代表双向链表当前节点指向前后节点的引用。如下图:

阿里二面:java8的stream api是迭代一次还是迭代多次

面试官:上面用双向链表把所有操作都串联起来了,这样可以实现从Head节点开始依次执行所有的操作。但是这些操作怎么叠加在一起呢?比如下面这段代码有三个map方法,后面的方法要依赖前面的计算结果:

List list = Arrays.asList(5, 2, 3, 1, 4);

List newArray = list.stream().map(StreamTest2::map1).map(StreamTest2::map2).map(StreamTest2::map3).collect(Collectors.toList());

:Stream提供了Sink接口来处理操作的叠加。上面代码的map方法把操作封装到了Sink,每个节点执行操作时,调用Sink的accept方法就可以把操作结果传给下一个节点的Sink。比如map方法源代码如下:

public final Stream map(Function<? super P_OUT, ? extends R> mapper) {

Objects.requireNonNull(mapper);

return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,

StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {

@Override

//返回包装成的Sink

Sink<P_OUT> opWrapSink(int flags, Sink sink) {

return new Sink.ChainedReference<P_OUT, R>(sink) {

@Override

public void accept(P_OUT u) {

//downstream是下游节点的Sink,把当前节点的执行结果传给下游节点

downstream.accept(mapper.apply(u));

}

};

}

};

}

面试官:能详细讲一下Sink吗?

:Sink主要提供了下面4个方法

//执行操作之前调用这个方法

void begin(long size)

//执行操作之后调用这个方法

void end()

//是否可以结束操作

boolean cancellationRequested()

//操作执行函数

void accept()

对于有状态的操作,必须实现begin和end两个方法,因为begin方法会创建一个存放中间结果的容器,accept方法将元素放入该容器,end方法负责对容器中元素处理,比如排序。

面试官:那cancellationRequested方法什么时候用呢?

:这个方法用于短路操作,比如stream.findAny。

面试官:你刚刚提到短路操作,怎么区分短路操作和非短路操作呢?

总结

在清楚了各个大厂的面试重点之后,就能很好的提高你刷题以及面试准备的效率,接下来小编也为大家准备了最新的互联网大厂资料。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

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

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

1715684484213)]

[外链图片转存中…(img-QpNvfuhK-1715684484213)]

[外链图片转存中…(img-LGZZpEp7-1715684484213)]

[外链图片转存中…(img-ZgKwKg15-1715684484213)]

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值