
1. 循环结构在Java中的地位以及为什么偏偏是while和do while先聊一个很多人都忽略的问题Java里的循环语句明明有三种——for、while、do while但我在带新人或者面试候选人的时候发现大部分人对for循环用得贼溜一碰到while就开始含糊do while更是十个人里有八个答不清楚执行顺序。这其实是个挺危险的信号。循环结构是整个Java基础里最高频使用的语法之一而while和do while恰恰对应着两种完全不同的业务场景。你写一个读取文件直到EOFEnd of File的逻辑写一个用户输入校验的重试逻辑写一个游戏主循环几乎绕不开while而某些必须先执行一次再判断的场景比如菜单展示、密码确认do while才是正解。这篇内容我打算直接把这两种结构掰开揉碎讲一遍。不光是语法怎么写更会聊它们各自适合解决什么问题、运行流程到底是什么样的、跟for怎么取舍以及写的时候最容易踩的坑。适合Java初学者建立扎实的循环思维也适合准备面试的朋友把基础概念梳理清楚。如果你已经在工作中写了好几年Java但从来没有认真想过“为什么这里用while而不是do while”这篇同样值得看。注意本篇文章所有示例都基于标准JDK 8及以上版本不涉及任何第三方框架复制到本地就能直接跑。2. while循环的语法、执行流程与典型应用场景2.1 语法结构先判断后执行while循环的语法非常简单while (条件表达式) { // 循环体 }执行流程只有一句话先判断条件表达式的结果如果是true执行循环体执行完再回到条件判断如此反复直到条件变为false循环结束继续向下执行。这里有个初学者特别容易忽略的点while后面的条件表达式必须放在小括号里而且小括号后面不能加分号。如果你写了while (i 10); { System.out.println(i); i; }这在语法上是合法的——它会被解析成一个空的循环体也就是while (i 10);这句本身就在空转而后面的{...}变成了一个独立的代码块跟循环没有任何关系。一旦i的值不发生变化这个循环就陷入了死循环程序卡死。我见过好几个新人在生产代码里犯过这个错误排查半天才找到问题。另一个要点是如果循环体只有一条语句大括号可以省略。比如while (i 10) i;这种写法虽然合法但我强烈建议永远不要省略大括号。一是可读性差二是后续在循环体里加第二行逻辑时新手经常忘记补大括号导致逻辑错位却不容易发现。我在团队里定过一个规矩循环、判断一律带大括号没有例外。2.2 执行流程的细节条件表达式在循环体之前求值理解while的关键在于理解它的判断时机。用一句话概括**先看门再进门。**如果一开始条件就不满足循环体一次都不会执行。举例int count 10; while (count 10) { System.out.println(这里不会输出); }count初始为10条件是count 10结果直接为false所以循环体完全不会执行。这看起来很简单但在实际代码里却经常引发bug。比如你想对一个列表做非空判断后遍历ListString list getList(); while (list.size() 0) { String item list.remove(0); System.out.println(item); }如果getList()本身返回的就是空列表循环体一次都不会执行这其实正是你想要的“安全”行为。但很多人写代码时意识不到while天然带有“防御性”它保证了在最坏情况下条件一开始就不成立你的程序不会崩溃只是什么也不做而已。这种特性让while特别适合处理那些“不知道要循环多少次甚至可能一次都不需要循环”的场景。最典型的就是读取数据源BufferedReader reader new BufferedReader(new FileReader(data.txt)); String line; while ((line reader.readLine()) ! null) { // 处理每一行 }这里readLine()在文件读取完毕后会返回null所以while的条件天然帮我们判断了“是否还有下一行”。如果文件本身是空的第一次判断readLine() ! null就是false循环体一次都不会执行程序正常结束不会有任何异常。这个模式在Java IO读取、JDBC结果集遍历、消息队列消费里简直无处不在。2.3 典型场景不确定次数的轮询与重试机制我用得最多的一种while场景是重试机制。比如调用某个外部接口可能偶尔失败我们希望失败后隔一段时间再试最多试N次int maxRetries 3; int attempt 0; boolean success false; while (attempt maxRetries !success) { try { System.out.println(第 (attempt 1) 次尝试执行任务...); // 模拟可能失败的操作 if (new Random().nextInt(10) 3) { throw new RuntimeException(模拟异常); } success true; System.out.println(任务执行成功); } catch (RuntimeException e) { attempt; if (attempt maxRetries) { System.out.println(失败准备重试...); } } } if (!success) { System.out.println(重试 maxRetries 次仍然失败请人工介入); }这段逻辑里while的条件同时判断了两个维度一是有没有达到最大重试次数二是是否已经成功。两个条件用连接一旦success变为true整个循环立即终止。这种写法比在循环体里break跳出来要清晰得多因为循环的“终止条件”被完整地写在了while那一行读代码的人一眼就能看出意图。这里面有个经验把循环的退出条件尽可能地放在while后面的条件表达式中而不是放在循环体里用break硬跳。原因很简单break跳转没问题但它只告诉你“循环在这里被打断了”却没有告诉你“为什么被打断”。如果循环体里写着三四个break读代码的人需要逐个看才能搞清楚所有可能的退出路径。而把条件都写在while那里代码的意图一目了然。3. do while循环先执行一次再判断条件3.1 语法结构do在前while判断在后do while的语法长这样do { // 循环体 } while (条件表达式);注意这里的细节while关键字后面必须加分号这是很多新手写错的地方。而while结构在写循环体大括号时while (条件)后面没有分号两者正好相反。这块特别容易混我在面试Java实习生的时候经常拿这个小点做区分do while的分号是语法的一部分漏掉直接编译报错。执行的流程跟while最大的区别在于**先执行一次循环体然后再判断条件。**用一句话概括不管门开没开先一脚迈进去出来之后再决定要不要再来一次。所以do while有一个非常确定的特性**循环体至少会执行一次。**这个特性是它与while唯一的结构性差异也是我们选型时的决定性依据。3.2 为什么需要“至少执行一次”的循环很多新手会觉得do while有的功能我用while加一个前置标志位也能实现啊何必多此一举确实可以比如// 用while模拟do while boolean first true; while (first || condition) { first false; // 循环体 }但这种写法绕来绕去可读性极差而且first这个标志位的存在是个隐患——如果你忘了在循环体的末尾重置它或者中间有continue跳过赋值逻辑就会变得诡异。所以Java保留do while绝不是为了凑语法数量而是为了让“至少执行一次”这个语义能直接表达出来。do while最经典的场景是用户输入校验。假设你要写一个程序让用户输入一个正整数如果输错了就重新输入直到输入正确为止Scanner scanner new Scanner(System.in); int number; do { System.out.print(请输入一个正整数: ); while (!scanner.hasNextInt()) { System.out.println(输入格式错误请重新输入); scanner.next(); // 清除错误输入 } number scanner.nextInt(); if (number 0) { System.out.println(必须大于0请重新输入); } } while (number 0); System.out.println(你输入的是: number);这段代码的核心逻辑是先让用户输入一次然后判断符不符合要求不符合就循环重新输入。这里用do while是天然的合适——你总得让用户先输入一次才能判断输入是否合法。如果换成while你就要在循环前先写一次输入代码循环体里再写一次造成完全重复的两段代码维护起来很痛苦。另一个高频场景是菜单循环。做控制台小项目时菜单必须至少展示一次给用户看用户选了退出才结束Scanner scanner new Scanner(System.in); int choice; do { System.out.println( 学生管理系统 ); System.out.println(1. 添加学生); System.out.println(2. 删除学生); System.out.println(3. 查看所有学生); System.out.println(0. 退出系统); System.out.print(请选择: ); choice scanner.nextInt(); switch (choice) { case 1: System.out.println(执行添加学生逻辑...); break; case 2: System.out.println(执行删除学生逻辑...); break; case 3: System.out.println(执行查看学生逻辑...); break; case 0: System.out.println(正在退出系统...); break; default: System.out.println(无效选项请重新选择); } } while (choice ! 0);这个场景里菜单界面必须首先展示给用户看然后用户做出选择根据选择决定是否退出。用do while天然契合用户的操作流程。我当年做课程设计的时候很多同学用的是while(true)加break跑起来虽然没问题但逻辑明显不如do while干净。3.3 while和do while的核心差异对比我把两者的核心差异整理成一个表格建议你收藏这份对照对比维度whiledo while判断时机先判断条件再执行循环体先执行循环体再判断条件执行次数可能0次至少1次语法末尾条件后不需要分号while(条件)后必须有分号适合场景不知道循环次数甚至可能一次都不需要循环至少需要执行一次再根据条件决定是否重复典型应用读取文件、遍历数据源、重试机制用户输入校验、菜单展示在职场上你可以这么记如果业务逻辑允许“一次都不做”就用while如果业务逻辑要求“先做一次再说”就用do while。这个判断准则说起来简单但我在code review时经常发现选错的情况。最典型的是有人把用户输入校验写成// 错误示范用while写输入校验 System.out.print(请输入密码: ); String password scanner.next(); while (!password.equals(123456)) { System.out.print(密码错误请重新输入: ); password scanner.next(); }这两段逻辑本身没毛病但你在循环外已经写了一次输入代码循环内又写了一次。万一后面密码校验逻辑从“等于123456”变成“长度不低于8位且包含字母和数字”你得改两个地方。而用do while只需要写一份输入代码校验逻辑全在条件表达式里。能用do while的地方别用while硬凑这是减少重复代码的一个好习惯。4. 实操完整实现一个while与do while结合的小项目4.1 项目需求与设计思路光讲语法太干我直接带你过一遍我实测验证过的完整小项目——记账本控制台程序。这个项目的逻辑刚好能同时用到while和do while两种结构。需求很简单用户通过控制台输入收入或支出每次记录包含金额和备注主菜单循环展示用户可以反复操作输入特定命令退出程序这里的设计思路是主菜单用do while保证先展示菜单给用户菜单里处理单次输入的业务逻辑用while做数据校验保证用户输入的数据合法。4.2 完整代码实现与运行结果import java.util.ArrayList; import java.util.List; import java.util.Scanner; public class MyLedger { private static class Record { String type; // 收入 或 支出 double amount; String note; Record(String type, double amount, String note) { this.type type; this.amount amount; this.note note; } Override public String toString() { return type : amount 元 ( note ); } } public static void main(String[] args) { Scanner scanner new Scanner(System.in); ListRecord records new ArrayList(); int choice; do { System.out.println(); System.out.println( 我的记账本 ); System.out.println(1. 记一笔收入); System.out.println(2. 记一笔支出); System.out.println(3. 查看所有记录); System.out.println(4. 查看当前收支总额); System.out.println(0. 退出程序); System.out.print(请输入您的选择: ); while (!scanner.hasNextInt()) { System.out.println(输入的不是数字请重新选择:); scanner.next(); } choice scanner.nextInt(); switch (choice) { case 1: addRecord(scanner, records, 收入); break; case 2: addRecord(scanner, records, 支出); break; case 3: if (records.isEmpty()) { System.out.println(目前还没有任何记录~); } else { System.out.println(--- 全部记录 ---); for (Record r : records) { System.out.println(r); } } break; case 4: double total 0; for (Record r : records) { if (收入.equals(r.type)) { total r.amount; } else { total - r.amount; } } System.out.println(当前结余: total 元); break; case 0: System.out.println(正在退出记账本感谢使用); break; default: System.out.println(无效选项请重新输入); } } while (choice ! 0); scanner.close(); } private static void addRecord(Scanner scanner, ListRecord records, String type) { double amount; do { System.out.print(请输入金额必须大于0: ); while (!scanner.hasNextDouble()) { System.out.println(输入的不是数字请重新输入:); scanner.next(); } amount scanner.nextDouble(); if (amount 0) { System.out.println(金额必须大于0请重新输入); } } while (amount 0); System.out.print(请输入备注: ); String note scanner.next(); records.add(new Record(type, amount, note)); System.out.println(添加成功); } }这个程序我实际在本地跑过一遍主循环用do while包裹整个菜单展示和操作处理天然满足“先展示菜单再选择”的逻辑。而在addRecord方法中金额校验用do while保证用户至少输入一次金额输入不合法就循环重新输入直到合法为止。程序有两个细节值得说明。第一scanner.hasNextInt()和scanner.hasNextDouble()这两个方法在这里起到“防范输入炸弹”的作用。如果你直接调用nextInt()而用户输入了abc程序会抛出InputMismatchException。用while (!scanner.hasNextInt())先做类型校验配合scanner.next()把错误输入消费掉程序就不会崩溃这也是控制台交互程序里必会的技巧。第二退出菜单用的是choice ! 0作为条件。用户输入0后switch里执行了“正在退出”的提示然后循环条件判断为false整体结束。这里switch里并没有用break跳出循环而是让条件判断去决定退出逻辑更清晰。如果你试图在case 0里写break那只能跳出switch跳不出while循环——这也是一个高频面试考点。4.3 这段代码能教给你什么这个记账本项目最大的价值在于它让你同时看到两种循环的协作关系**外层do while负责“至少展示一次菜单并持续服务”内层do while负责“金额至少输入一次且必须达标”内层while负责“挡住非数字输入”三个循环各有分工各司其职。你把这段代码复制到你的IDE里跑一遍然后试着修改成学生成绩管理系统、购物车结算系统或者图书借阅系统思路是完全通用的。实操心得我在本地用IDEA跑这段代码时刻意测试了几种异常输入直接输入负数金额、输入abc字符串、菜单输入99。意外的是程序全部稳住了没有抛异常也没有死循环。这正是whiledo while组合起来处理用户输入的可靠之处。5. 死循环、边界条件与高频面试题5.1 死循环是while最大的坑但也可以是武器while最常见的问题就是死循环。新手最典型的死循环代码长这样int i 0; while (i 10) { System.out.println(i i); // 忘记写 i; }由于i永远不会变化条件永远为true程序会一直打印下去直到你手动中断。这个错误我几乎在每一个新学员身上都见过所以我现在看代码时格外注意循环体里有没有“推进变量”的逻辑。但有意思的是死循环本身并不总是坏事。在很多底层系统里死循环是刻意为之的。比如消息队列的消费者while (true) { Message msg consumer.receive(); if (msg ! null) { process(msg); } }这里写while (true)是为了让消费者进程常年运行不退出而在循环体里通过break或return来控制退出时机。但请你注意即使是刻意死循环也一定要确保循环体内部有可控的退出路径并且最好加上一些条件判断避免在异常情况下真的变成无法退出的“野马”。另外在写while (true)的时候我习惯在循环体开头就思考一个问题这个循环什么时候会结束如果答案不清晰那这段代码八成有问题必须停下来重构。5.2 边界条件循环中的“差一错误”循环结构里最经典的bug就是off-by-one也就是差一错误。看这段代码// 期望输出0到9 int i 0; while (i 10) { System.out.println(i); i; }条件写成i 10输出的就是0到10多了一个10。正确写法应该是i 10。这种错误特别容易在你手工推导循环次数时出现因为人脑默认“到10结束”但循环条件是判断“当前值是否满足”所以很容易把和搞混。我的建议是**写循环条件之前先在注释里写明循环的边界语义。**比如// 循环次数: 10次, i取值为[0, 10)左闭右开 int i 0; while (i 10) { System.out.println(i); i; }这种注释不用每行都写但在循环条件不那么直观的时候一行注释能帮你和后来者省下大量推演时间。三维数组遍历、棋盘格子遍历、批处理分页等场景边界条件尤其容易出错建议多花几秒钟推演边界值特别是首元素和末元素是否被正确处理。5.3 高频面试题while与do while的区别及for的取舍面试题几乎必考一类问题“说说while和do while的区别”“什么时候用while什么时候用for”。while先判断后执行可能一次都不执行do while先执行后判断至少执行一次重点强调标志性语法差异do while的末尾while(条件)后有分号而while没有至于for和while的取舍我的个人建议是**明确知道循环次数的情况下优先for不确定循环次数的情况下优先while。**比如遍历一个固定大小的数组用for (int i 0; i arr.length; i)是最直观的而“持续读取直到没有数据”这种场景用while才是恰当的。有些人会质疑for (;;)不也是死循环吗对但for (;;)跟while (true)在功能上完全可以互换。只是从代码可读性上讲while (true)的意图更直白所以在我的团队规范里死循环统一写while (true)不写for (;;)。另外面试中还有一个加分回答方向**循环体内使用continue时while和do while的行为也有细微差别。**在while里当执行到continue时程序直接跳到条件判断处而在do while里continue跳转到while(条件)的判断处。关键区别在于如果你在do while的循环体末尾有变量递增语句而continue恰好在递增语句之前那么递增语句会被跳过容易造成意外的死循环。举例int i 0; do { if (i % 2 0) { i; // 手动推进 continue; // 跳过下面的代码直接判断 } System.out.println(i); i; } while (i 10);如果你不在continue之前手动推进i这个循环就会在i不变化的情况下永远转圈。这算是一个比较棘手的细节能答出来会让面试官高看一眼。5.4 常见问题速查表我把自己在写代码和带新人过程中遇到的典型问题整理成了一张速查表建议直接保存症状可能原因解决方案程序运行后没有任何输出就结束了while条件一开始就是false循环体未执行检查条件表达式是否应为do while程序卡死不动CPU占用飙升循环体内缺少变量推进或存在continue跳过推进检查循环体内是否有改变条件的语句循环次数总是多一次或少一次和用混差一错误明确边界语义用手工推演首尾值do while编译报错while(条件)后漏写分号补齐分号这是do while语法的一部分输入字母程序就抛异常直接调用nextInt()没有做类型判断先hasNextInt()校验再读取while(条件);后面代码不执行空循环体吞掉了循环后面成了独立代码块不要在while(条件)后加分号提示最后补充一个调试技巧——如果你不确定循环执行到哪里出了问题在循环体里临时加一行System.out.println(i)打印当前变量值配合IDEA的断点点看变量状态。这比死盯着代码干想要高效得多。排查完记得删掉这些调试输出保持代码整洁。6. 在项目中如何选择循环结构我的实操体会写到这里我想把最核心的选择逻辑再帮你梳理一遍。这不是语法层面的问题而是设计层面的经验总结。我在实际项目里选循环结构的时候心里默认有一条判断链路第一步看循环次数是否确定。确定且需要索引用for第二步不确定循环次数问自己一个问题这个逻辑允许“一次都不做”吗允许选while不允许必须至少做一次选do while这条链路看起来很粗但覆盖了绝大多数业务场景。比如批量导入Excel数据如果表格里可能有一万行也可能一行都没有那就用while遍历行因为空表时不需要执行任何处理又比如控制台登录验证你必须先让用户输入密码才能判断对不对那就铁定用do while不然代码就得重复写一遍输入逻辑。如果你还在初学阶段我建议你做一个小练习把同一个需求分别用while和do while各写一遍。比如“不断接收用户输入直到输入quit才退出”这个需求用while需要先定义初始变量再判条件用do while则天然贴合。两个版本都写完之后对比它们的代码量和可读性你会对这两种结构产生非常直观的体感。这个体感是看多少篇教程都替代不了的。最后再分享一个小技巧写循环代码前先在大脑里跑一遍完整的执行流程重点关注“第一次进入循环”“最后一次循环”“中间continue和break的影响”三个节点。这个习惯帮我少写了无数个outofbounds和死循环bug希望也对你有用。