第004篇 循环 for/while/do-while:遍历与终止的工程课

简介: Android面试中,循环看似简单,实则暗藏细节:终止条件、步长控制、对象分配、GC压力、快速失败机制、浮点误差、多层跳出等均是高频追问点。掌握for/while/do-while本质差异、增强for底层原理及工程避坑(如遍历中禁用list.remove),方能稳过此关。

Android 面试里,循环 for/while/do-while 看着简单,追问起来全是细节。它常被包装成一道开放题:"你在项目里是怎么用的?"这道题答不好,往往不是不会写循环,而是很少把"循环里到底发生了什么"讲清楚——从终止条件、迭代步长到循环内的对象分配,每一层都能延伸出追问。把循环从语法讲到代价,基本就稳了。

先把结论立住:循环的本质是"重复直到不满足"

一句话:循环就是"在条件满足时反复执行同一段逻辑",差别只在"先判断还是先执行"以及"迭代变量怎么推进"。for 把初始化、条件、步长收在一行,适合已知次数的遍历;while 先判断后执行,适合不确定次数、靠状态退出的场景;do-while 先执行一次再判断,保证主体至少跑一遍。

这里要先立住一个性能前提:循环内创建对象会放大 GC 压力,热点路径上应当把不变的对象或计算结果提升到循环外,只算一次。另一点是增强 for(for-each)底层依赖 Iterator 或数组索引,它把遍历细节藏起来了,也顺带规避了下标越界,但代价是不能在遍历中安全地结构性修改集合。

机制拆解:三种循环的终止与变体

for 循环的三个部分(初始化、条件、步长)在每次迭代前后各跑一遍,条件为假时退出。while 在每轮开头求值,条件一开始就为假则一次都不执行。do-while 把条件放到轮尾,主体至少执行一次——这正是它和 while 最本质的区别,也决定了它只适合"先做一次再看要不要继续"的场景。

增强 for 循环遍历集合时,实际调用的是 Iterator 的 hasNext/next;遍历数组时直接用下标。关键点在于:Iterator 在结构性修改(add/remove)时会抛出 ConcurrentModificationException,这是"快速失败"机制在保护你,而不是偶然报错。标签语法 break outer 可以一次跳出多层循环,但滥用会严重伤害可读性,能拆成方法返回的尽量拆。

代码里见真章

看一段能直接跑的代码,把上面的机制落到具体写法上:

// 循环:for / while / do-while 与终止、遍历、跳出
for (int i = 0; i < 10; i++) {
   
    if (i == 3) continue;          // 跳过本次,进入下一轮
    if (i == 8) break;             // 直接结束整个循环
}
System.out.println(i);             // 循环外仍可访问,值为 8

int[] xs = {
   1, 2, 3};
for (int v : xs) System.out.print(v + " ");   // 增强 for:只读遍历,不暴露下标

// do-while:先执行一次,再判断条件
int n = 0;
do {
    n++; } while (n < 0);          // 条件一开始就为假,但仍执行了一次

// 标签跳出多层循环(慎用:可读性代价高)
outer:
for (int a = 0; a < 3; a++) {
   
    for (int b = 0; b < 3; b++) {
   
        if (a == 1 && b == 1) break outer;   // 一次跳到 outer 之后
    }
}

// 增强 for 中结构性修改集合会触发快速失败
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for (String s : list) {
   
    // list.remove(s);   // 运行期抛 ConcurrentModificationException
}
// 正确做法:用迭代器或 removeIf
list.removeIf(s -> s.equals("b"));

这段代码值得盯三处:第一处,for 的 break/continue 控制的是"轮次",循环变量在循环外依然可见;第二处,do-while 即便条件一开始就为假也至少执行一次,这是它和 while 的分水岭;第三处,增强 for 里直接 remove 会触发快速失败,改用迭代器或 removeIf 才是正解。面试讲到这一层,基本就稳了。

最常见的几个坑

最常见的坑是在增强 for 里调用 list.remove 触发 ConcurrentModificationException,正确做法是用迭代器的 remove,或者 Java 8 之后的 removeIf。很多人栽在这,是因为把"遍历"和"修改"当成同一件事,而 Iterator 的设计本就是两者分离。

其次是 while 循环条件里调用有副作用的读取方法,读到末尾返回 null 才退出,容易漏掉资源关闭。比如一边读流一边判断,循环结束没进到 finally 去关流,连接和句柄就悄悄泄漏了。把资源的关闭放进 try-with-resources,比依赖循环退出后手动关更稳。

还有一个更隐蔽的坑:循环内的字符串拼接用 +=,每次都创建新对象并拷贝旧内容,在热路径上会迅速放大 GC 压力。应该改用 StringBuilder,或者把不变的部分提到循环外只拼一次。这不是风格问题,是能用 systrace 量出来的真实卡顿来源。

再一个是用浮点数做循环步长或终止判断,浮点累积误差会让循环次数和预期不符,甚至无限循环。计数类循环一律用整数索引,需要浮点步进时显式控制最大迭代次数兜底。

面试中的经典追问

"请简单介绍一下 for/while/do-while,它们在 Android 开发中起什么作用?"——先一句话说清三者区别:for 已知次数、while 先判后执行、do-while 至少一次;再拿项目场景证明你真用过,比如用 while 轮询下载进度、用 for 批处理列表。do-while 至少执行一次,标签语法 break outer 可一次跳出多层循环,但滥用会严重伤害可读性。按"结论、依据、边界"三步走最稳。

"循环的底层原理是什么?能不能详细说一下?"——别停在 API 表层:按动机、机制、代价讲。动机是"重复执行",机制是条件求值加迭代推进,代价是循环内分配带来的 GC、下标越界与快速失败。换个角度:如果让你给循环做一次性能画像,你会量哪些指标?迭代次数、单次耗时分布、分配对象数,这三项是起点。

"在使用循环时遇到过什么问题?"——讲问题先交代现场,再讲排查路径与根因,收尾给预防措施;全程围绕一次真实踩坑。比如曾在列表遍历里顺手 remove 导致偶发崩溃,稳定复现后用二分法定位到某个分支的修改,修复方式是改成 removeIf,并补了一条"遍历中禁止结构性修改"的团队约定。

"循环和相关的替代方案相比,有什么优劣?"——先列可选方案:增强 for、Iterator、Stream、递归;再按性能、可读性、可维护性逐项对比,最后给场景结论。选型时把"while 条件里调用有副作用的读取方法容易漏关资源"这类代价摆到台面上,再决定是否引入。

工程落地:真实项目里怎么用

循环在项目里的高频落点不少:一是批量持久化或网络请求时,用 for 分批,每批控制数量避免一次性打满内存;二是用 while 轮询任务状态或下载进度,退出条件必须明确且能兜底超时,防止死循环卡住主线程;三是列表处理优先增强 for 或 Stream,既省下标管理又规避越界;四是热路径上的循环把对象创建和不变计算提到循环外,必要时用 StringBuilder 累积。

一个稳妥的工程约定:任何 while 循环都必须有"超时或最大次数"兜底;遍历中需要删除元素一律走迭代器或 removeIf;循环体内的不变分配提到外层;超过三层或逻辑复杂的循环体抽成独立方法,让主流程保持可读。把这几条固化成 lint 规则,比靠 code review 人盯人稳得多。

还有一处值得量化的细节:for 循环的初始化变量作用域只在这一层循环内,而把计数器声明在循环外会在方法栈上多留一个存活槽位。绝大多数时候无足轻重,但在被 JIT 内联的热点方法里,减少栈上存活变量能帮助逃逸分析更激进地优化对象分配。由此得到一个提醒:循环写法在"能跑"之上,还藏着可被 JVM 优化的空间,热点路径值得用 Profiler 实际看过再下结论,而不是凭感觉选写法。

给正在准备面试的你

理解循环之后,需要在真实项目里练一遍:写一段增强 for 遍历并故意在循环里 remove,看它抛什么、为什么抛;再写一段 while 轮询加超时兜底,确认它不会卡死。这两步做下来,比再读三遍语法书都管用。

如果只记两句话,就记这两句:第一,增强 for 中结构性修改集合会触发快速失败,删除走迭代器或 removeIf;第二,任何 while 都必须有超时或最大次数兜底,循环内的不变分配提到外层。把这两句讲顺,循环这一关就过了。


如果这篇文章对你有帮助,欢迎点赞、在看、转发三连。你的支持就是这个系列持续更新的动力。

「Android 软件开发面试·从入门到精通」连载系列

上一篇:流程控制-if-else-与-switch:分支逻辑规范写法

下一篇预告:数组与多维数组:批量数据的地基

有任何问题欢迎在评论区留言交流。

相关文章
|
JSON 小程序 JavaScript
【微信小程序】-- 自定义组件 - 组件所在页面的生命周期 & 插槽(三十七)
【微信小程序】-- 自定义组件 - 组件所在页面的生命周期 & 插槽(三十七)
|
3月前
|
机器学习/深度学习 人工智能 编解码
AI视频生成技术全解析:从一段文字到一段视频AI是怎么做到的
AI视频生成是如何实现的?本文从技术原理角度解析AI视频从文本描述到动态视频的完整流程,包括扩散模型、时序建模等核心技术。
792 0
|
4月前
|
SQL 关系型数据库 MySQL
MySQL版本升级最佳实践:从5.7到8.0再到8.4 LTS的兼容性审计与迁移策略
以一次真实升级事故开篇,覆盖MySQL 5.7→8.0→8.4升级路径、LTS与Innovation双轨线、兼容性审计、高危变更、升级路径对比与灰度切换策略
主机管理系统-五大免费主机管理系统分享
在服务器运维过程中,主机管理系统有着极其重要的作用,不仅能减轻管理员的工作量、加强规范化管理,优秀的主机系统还能提升服务器安全。虽然现在国内有不少收费版主机系统,但是对于需要有主机管理系统而要求又不是很高的用户来说,能省则省总是好事。
|
11月前
|
自然语言处理 搜索推荐 安全
2025数字人平台应用解析:各数字人定制场景化实践与技术特点
“来灵数字人”由苏州咖豆信息研发,聚焦数字人技术的实用化落地,推动其从形象展示向智能交互升级。平台具备系统协同、多模态交互、数据安全、行业知识嵌入与持续优化能力,已在零售、教育、金融等领域实现应用,提升服务效率与用户体验,助力企业数字化转型。
371 4
|
7月前
|
边缘计算 监控 Serverless
基于 Serverless 与云边协同的 Mobile Agent 架构:侠客工坊技术解析
本文介绍“侠客工坊”提出的云边协同Mobile Agent架构,以解决云原生时代移动端执行断层问题:通过Serverless事件驱动调度、端侧轻量Vision-SLM视觉推理、全链路多模态可观测性及内核级零侵入输入,实现高可用、可监控、合规的移动智能自动化。
574 0
|
8月前
|
SQL BI 数据库
企业智能问数平台的真正分水岭:本体语义层与预置指标层到底差在哪?
企业智能问数平台成败关键不在大模型或界面,而在于底层数据治理逻辑:是构建“预置指标层”(稳态可控、适合成熟BI体系),还是打造“本体语义层”(弹性扩展、支撑跨域复杂分析)。选型需权衡建设成本、维护负担与长期演进能力。
|
8月前
|
算法 新能源 API
路径规划-路线规划-驾车路径规划-步行路径规划-骑乘路径规划-公交路径规划API接口介绍
本文介绍地图路径规划API,支持驾车、步行、公交、骑行及电动车五种出行方式的智能路线计算。涵盖多策略算路(如避拥堵、少收费)、途经点/限行规避等高级功能,适用于无地图展示的线路查询或自研导航场景,助力开发者快速集成精准导航能力。
779 2
|
11月前
|
人工智能 安全 Go
Daytona:90ms 启动的 AI 代码沙箱基础设施
Daytona 是专为 AI 时代打造的代码执行基础设施,90 毫秒内创建多语言隔离沙箱,支持 Python、Node、Go 等,结合容器预热池与状态持久化,实现安全、高速、可复用的代码运行环境,助力 AI Agent、在线教育、CI/CD 等场景高效落地。
|
9月前
|
SQL 人工智能 自然语言处理
指标中台选型技术实测:如何通过 NoETL 语义层驾驭复杂 SQL 生成
支持“存量挂载、增量原生、存量替旧”的渐进式策略,平衡价值与风险,平滑实现架构升级。

热门文章

最新文章