ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JavaSE学习笔记Day03:流程控制、分支循环与常见排错实践

JavaSE学习笔记Day03:流程控制、分支循环与常见排错实践 第三天的JavaSE学习笔记内容锁定在流程控制。如果你是按着视频或者教材学Java大概第二天会把变量、数据类型、运算符过完第三天就开始写带判断、带循环的程序。我个人的感受是这一天才是真正接触“写程序逻辑”的起点因为前面的代码基本是从上往下顺着执行没什么花样从流程控制开始程序有了分支、有了重复才谈得上用代码解决实际场景里的问题。这篇笔记我按自己的学习进度整理先讲清楚流程控制在JavaSE里的地位再拆解if/switch和三大循环的写法与区别然后用几个经典小题目把这些知识点串起来最后把当天踩过的坑和排查方法一并记下来。后面学到数组、方法、面向对象其实都建立在Day03的思路上花一整天把它吃透是值得的。这篇内容既适合刚学到第三天的初学者查漏补缺也适合想快速回顾分支循环知识点的朋友直接看结论。1. 先搞清楚为什么第三天的重点是流程控制学Java到第三天很多人会有一个困惑前面学的变量、数据类型、运算符能写的东西无非是“声明一个变量算一个数打印一串输出”全是平铺直叙感觉不像在写程序。我自己当时也有这个感觉直到写了if和for才突然明白前面的语法知识都是为了流程控制做铺垫的。1.1 JavaSE学习路线里的位置如果把JavaSE基础部分排个顺序大致是这样Day01Java是什么、安装JDK、配置环境变量、写第一个HelloWorldDay02变量、基本数据类型、运算符、Scanner键盘录入Day03流程控制也就是分支和循环Day04之后数组、方法、面向对象、常用API、集合、异常、IO……Day03刚好处在“储备语法”和“用逻辑编程”的分水岭上。第二天学的运算符最常出现的地方不是赋值语句而是if后面的判断条件Scanner也不是为了录入而录入而是为了根据输入决定程序走哪条逻辑。没有流程控制数据就只能存进去、算出来、打出来有了流程控制程序才可以根据不同情况“选择”做不同的事。从知识点上看Day03的内容并不多掰着指头数就是if/else、switch、for、while、do-while再加上break、continue。但难点不在语法本身而在“怎么把现实问题翻译成条件判断和循环逻辑”。这个翻译能力恰恰是后面写算法、写业务代码的核心能力。1.2 程序的三种基本结构任何复杂的程序拆到最底层都只有三种结构顺序、分支、循环。顺序结构就是从上到下逐行执行这个从HelloWorld开始就一直在用Day02的代码基本都是顺序结构。分支结构是在程序里设置岔路口根据条件选择某一段代码执行对应Java里的if和switch。循环结构是让某段代码重复执行直到条件不满足对应for、while、do-while。我第一次听“三种基本结构”这个说法是在大学教材里后来工作多年再回头看发现这个总结特别经典。不管多复杂的框架、多庞大的业务系统落到方法体里面依然逃不出这三种结构的组合。所以Day03看似简单实际上是整个编程逻辑的地基地基没打牢后面写复杂代码很容易绕晕。2. 分支结构if和switch怎么选分支结构是让程序“有脑子”的第一步。Java里实现分支的工具有两个if系列和switch。它们不是对立关系而是互补关系理解清楚各自的适用场景写出来的代码自然更清晰。2.1 if的三种写法与边界条件if在Java里有三种常见形态。第一种是单独的if判断成立就执行不成立就跳过int score 85; if (score 60) { System.out.println(及格了); }第二种是if-else二选一int score 55; if (score 60) { System.out.println(及格了); } else { System.out.println(不及格); }第三种是if-else if-else多选一int score 78; if (score 90) { System.out.println(优秀); } else if (score 80) { System.out.println(良好); } else if (score 60) { System.out.println(及格); } else { System.out.println(不及格); }写if的时候有一个细节最容易出问题条件顺序。拿上面的成绩分级来说如果把条件倒过来写先判断score 60再判断score 90那么85分进入第一个分支后就直接结束了根本到不了优秀这个分支。所以多个else if并列时要保证条件的范围从窄到宽或者按互斥的区间去划分否则会出现“逻辑短路”。还有一个常见的坑叫“else悬空”。比如if (a 1) if (b 2) System.out.println(A); else System.out.println(B);这段代码里的else到底和哪个if配对Java的规则是else与最近的if配对所以这里else其实属于内层的if(b 2)而不是外层。我在初学阶段就吃过这个亏当时怎么调试都发现输出不对。后来养成了一个习惯无论代码多简单if和else都写大括号。这样既避免配对歧义也让代码更易读。在判断条件本身还有几个我记在笔记里的要点判断两个整数是否相等用注意不能写成否则是赋值判断两个double是否相等不要直接用因为浮点数有精度误差比如0.1 0.2 0.3的结果是false判断字符串是否相等不能用要用equals()方法判断boolean变量可以直接写if (flag)不用写if (flag true)。这些细节在写判断条件时非常容易踩坑后面第5章的排错部分还会展开说。2.2 switch/case的常规写法和JDK 14箭头语法switch是另一种分支结构适合做“固定值的多路选择”。最早学的是老式写法int week 3; switch (week) { case 1: System.out.println(星期一); break; case 2: System.out.println(星期二); break; case 3: System.out.println(星期三); break; default: System.out.println(其他); }老式写法有几个必须注意的规则。case后面必须是常量不能是变量每个case分支最好都写break否则会发生“case穿透”。case穿透的意思是执行完当前case分支后如果没有break程序会继续往下执行下一个case的代码直到遇到break或switch结束。case穿透不完全是坏事有些场景还专门利用它。比如我想根据成绩等级输出不同评价switch (grade) { case A: case B: System.out.println(表现不错); break; case C: System.out.println(还需要加油); break; default: System.out.println(没参加考试); }这里把A和B合并在一起处理就是故意利用穿透。但是新手阶段最容易出问题的是每个case忘了写break导致输出结果“多出来”好几行我建议先老老实实每个case都写break等理解了穿透机制再根据业务情况决定是否合并。JDK 14开始switch有了新写法用箭头代替冒号而且天然不会穿透switch (week) { case 1 - System.out.println(星期一); case 2 - System.out.println(星期二); case 3 - System.out.println(星期三); default - System.out.println(其他); }如果每个分支不是简单的一行输出也可以写成带大括号的代码块。箭头写法还支持把多个值合并switch (week) { case 1, 2, 3, 4, 5 - System.out.println(工作日); case 6, 7 - System.out.println(休息日); default - System.out.println(非法日期); }如果你用的是JDK 8或者低版本就不要在项目里写箭头语法编译会直接报错。确认自己环境版本的方法很简单在命令行执行java -version。我学习时用的环境是JDK 17箭头语法实测能正常编译运行但在教学案例里我通常两种写法都记一下毕竟老项目里还是以传统写法居多。2.3 什么时候用switch而不是if这是我第一次写分支代码时最纠结的问题。同一个逻辑用if能写用switch也能写到底选哪个我的经验是看条件类型和条件数量。如果条件是范围判断比如成绩大于90、年龄在18到30之间这种必须用if因为switch只能匹配固定值没法表达区间。如果条件是固定的几个值比如星期几、月份、菜单选项、枚举值用switch会比一串if-else if更直观可读性更好。另外从JDK 7开始switch支持String类型所以很多根据字符串做路由的场景也可以用switch。不过要注意switch做的是引用相等比较底层用的是hashCode和equals写法和基本类型一样但不要把它当成字符串内容的“包含”判断它只是精确匹配。还有一个选型上的建议如果分支只有两三个用if就够了硬上switch反而显得啰嗦如果分支超过五个且都是固定值switch的清晰度优势就体现出来了。选择标准不是“哪个功能强”而是“哪个让别人一眼能看懂”。3. 循环结构for、while、do-while的完整拆解循环解决的问题是“重复劳动”。程序里凡是需要重复执行的逻辑都离不开循环。Java提供了三种循环语句for、while、do-while它们语法不同适用的场景也有差别。3.1 for循环的四个部分和执行顺序for循环的语法结构如下for (初始化语句; 循环条件; 步进语句) { 循环体; }一个标准的for循环包含四个部分初始化、条件判断、循环体、步进。以打印1到5为例for (int i 1; i 5; i) { System.out.println(i); }执行顺序是先执行初始化语句int i 1然后判断i 5成立就执行循环体再执行步进i接着再判断条件……如此反复直到条件不满足才退出。也就是说循环体执行次数和条件判断次数是不相等的最后一次判断是“为了退出”才做的。我记这个顺序花了点功夫后来画了个简单的执行流程图才彻底理顺。初学者经常误以为for循环的执行顺序是“初始化→判断→步进→循环体”实际是“初始化→判断→循环体→步进”步进是在循环体执行完之后才发生的。for循环还有一个初学者容易忽略的点初始化语句里声明的变量i作用域只在for循环内部循环结束后i就访问不了了。如果想在循环结束后还要用到最终的值需要把变量声明在for外面int i; for (i 1; i 5; i) { System.out.println(i); } System.out.println(i的最终值是 i);这一步输出是6因为i是循环体执行完毕后触发的i5时条件成立执行循环体然后i变成6再判断就不成立了。理解这个细节很多循环相关的输出题就不会做错。嵌套循环也是for的重头戏。所谓嵌套就是循环体里面再放一个循环。比如打印一个3行4列的星号矩阵for (int i 1; i 3; i) { for (int j 1; j 4; j) { System.out.print(* ); } System.out.println(); }外层循环控制行数内层循环控制列数。内层循环每完整执行一轮相当于打印一行每执行完一轮外层就执行一次println换行。嵌套循环的循环次数是相乘关系3乘4等于12次这是判断循环复杂度的基本思路。后面学到算法时间复杂度嵌套循环相乘的思想会一直伴随你。3.2 while与do-while先判断还是先执行while循环的结构是“先判断后执行”int i 1; while (i 5) { System.out.println(i); i; }while适合循环次数不确定、只知道终止条件的场景。典型例子是“从键盘一直输入数字输入0就结束”这种场景你不知道用户会输入多少次所以没法用for写死次数。do-while的结构是“先执行后判断”int i 1; do { System.out.println(i); i; } while (i 5);do-while和while的唯一区别是do-while无论条件是否成立循环体至少执行一次。这个“至少一次”的特性在某些业务场景下很重要。比如登录系统的密码校验总要让你先输入一次密码再判断对不对如果密码正确直接结束如果错误再提示重新输入这种“先做一次再判断要不要继续”的逻辑天然适合do-while。判断条件为false时while直接不执行循环体而do-while会先执行一次再判断。我用一个例子来记住它们while像门卫查证件查完不合格直接不让你进去do-while像先请你进大厅走完一圈流程再查证件不合格就赶出去但你已经进去过一回了。哪个场景用哪个循环我的笔记里是这样总结的明确知道循环次数用for不知道次数但知道结束条件用while要求无论如何先执行一次循环体用do-while。for和while其实可以互相转换但代码的可读性有差别。循环变量在固定范围内递增递减用for更清晰只靠一个不断变化的布尔条件来控制是否继续用while更自然。3.3 break、continue、return的分工循环里还有三个控制关键字break、continue、return。它们的职责完全不同初学者特别容易混。break的作用是“终止整个循环”。比如我要找到1到100之间第一个能被7整除的数找到后就没必要继续循环了for (int i 1; i 100; i) { if (i % 7 0) { System.out.println(找到了 i); break; } }continue的作用是“跳过本次循环的剩余代码直接进入下一次循环”。比如打印1到10之间所有偶数for (int i 1; i 10; i) { if (i % 2 ! 0) { continue; } System.out.println(i); }当i是奇数时continue会立刻结束本次迭代后面的println不会执行直接进入i。注意这里有一个细节continue之后步进语句仍然会执行不会导致死循环。return的作用是“结束整个方法”。比如我想在代码中找到第一个满足条件的数然后不再执行方法后面任何代码就可以用returnfor (int i 1; i 100; i) { if (i % 7 0) { System.out.println(找到了 i); return; } } System.out.println(这段代码只有for循环一次都没进入时才会执行);三个关键字可以记成一个口诀break是打断这一层循环continue是继续下一轮return是直接走人。在嵌套循环里break只能跳出它所在的这一层循环不能直接跳出外层。如果想跳出多层循环常规做法是给外层循环加标签如outer: for(...)然后在内部用break outer;跳出。这个写法建议了解一下实际业务里用到嵌套循环加标签跳出的场景不多但面试题偶尔会出现。4. 实操用Day03知识完成3个经典练习学习流程控制的第二天我的老师其实是我跟的视频教程出了好几个练习题。当时觉得这些题目很基础后来工作经验多了才发现这些题就是算法思维的雏形。每一个题目都值得亲手敲一遍而不是盯着屏幕看懂了就划走。4.1 猜数字小游戏题目要求程序随机生成一个1到100之间的整数用户从键盘输入数字猜测。猜对了提示“恭喜猜对”猜错了提示“猜大了”或“猜小了”直到用户猜对为止。这个题串起来的知识点包括Random随机数、Scanner键盘录入、while循环、if分支、break跳出。核心代码import java.util.Random; import java.util.Scanner; public class GuessNumber { public static void main(String[] args) { Random random new Random(); int target random.nextInt(100) 1; Scanner scanner new Scanner(System.in); while (true) { System.out.print(请输入你猜的数字); int guess scanner.nextInt(); if (guess target) { System.out.println(猜大了); } else if (guess target) { System.out.println(猜小了); } else { System.out.println(恭喜猜对了); break; } } } }写这个题有几个我踩过的坑。第一random.nextInt(100)生成的是0到99的整数要生成1到100必须加1这个边界非常容易忘记。第二while(true)是个死循环必须有break出口否则程序会一直转。第三猜数字是“先判断后决定是否继续”的逻辑用while比do-while更自然因为如果第一把就猜中完全没有必要再多执行一次。我当时还自己加了一个计数变量统计用户猜了多少次才猜中。这个扩展其实暗合了“循环次数不确定”的思想不知道用户会猜几次所以用一个变量累积次数直到break退出循环后再输出。4.2 九九乘法表九九乘法表适合练嵌套for循环和格式化输出。先从一行开始分析第n行应该输出“1×n”到“n×n”所以内层循环的循环条件不是固定的而是跟外层变量相关。for (int i 1; i 9; i) { for (int j 1; j i; j) { System.out.print(j * i (j * i) \t); } System.out.println(); }执行结果就是标准的左下三角九九乘法表。这里最关键的点是内层循环的终止条件是j i等于说第1行打印1个式子第2行打印2个式子第9行打印9个式子。我在做这个题目时一开始写的是j 9结果每行都打印9个式子看起来就不是乘法表。后来想明白一个通用规律当内层循环的次数和外层循环变量有关联时一定要检查“关联方向”。乘法表里内层次数随外层递增所以是j i如果内层次数随外层递减那就是j 9 - i 1之类的写法。这里还要注意System.out.print和System.out.println的区别。print是打印不换行println是打印后换行。乘法表里每打印一个式子用print打印完一行才用println很多人第一次写代码会混淆这两个方法导致输出全挤在一行或者一个式子占一行。4.3 水仙花数与不死神兔水仙花数练习的是循环与数学运算的结合。题目要求打印所有三位数中各位数字的立方和等于该数本身的数。比如153就等于1的立方加5的立方加3的立方。先把这个“取各位数字”的过程拆解清楚再考虑循环条件代码就不会乱for (int i 100; i 999; i) { int ge i % 10; int shi i / 10 % 10; int bai i / 100; if (ge * ge * ge shi * shi * shi bai * bai * bai i) { System.out.println(i); } }这个题的核心不是循环本身而是用取模和整除把数字拆开。i % 10取个位i / 10 % 10取十位这是以后写数字处理代码的通用套路。我记这个套路的方式是先从右往左每一次“除以10再取余数”就能剥离一位。不死神兔是另一个经典题也叫斐波那契数列。题目大致是一个月一对兔子生一对小兔子小兔子两个月后又能生问第n个月有多少对兔子。规律是第一个月1对第二个月1对第三个月2对第四个月3对往后每个月的数量等于前两个月之和。这类题用循环递推非常直观int n 20; int a 1; int b 1; for (int i 3; i n; i) { int temp a b; a b; b temp; } System.out.println(第 n 个月的兔子对数是 b);这个题的意义在于理解“状态更新”在循环里a和b不断往后移动相当于把前两个月的值递推成后一个月的值。temp变量是用来暂存相加结果的如果直接把b a b那b的值一改原来的b就丢了下一步a就没法更新。我当年在这类题上反复出错后来养成一个习惯在做“交换变量”或“滚动更新”时先在草稿纸上标出哪几个值要保留再决定要不要加临时变量。5. 调试与排错学流程控制最值得花时间的地方四道题做完我原本以为Day03的语法都掌握了结果写综合练习时各种报错和逻辑错误全跑出来。回头看这反而是收获最大的一段时间。流程控制的代码量不大但排查问题的过程正好帮我彻底理解循环和分支的执行顺序。5.1 六个常踩的坑第一个坑是循环条件方向写反。比如“从1加到100”的累加我一开始写的是i 1结果程序进入死循环。原因很简单i每次递增判断条件i 1永远成立循环就永远不退出。排查方法也简单在循环体里打印i看一眼i的走势。第二个坑是条件里把写成了。这个错误在Java里不会直接编译报错因为i 5这个表达式本身是合法的它把i赋值为5同时整个表达式的值是5非零就是true。于是本来要判断相等的地方变成了“永远为true”。尤其是判断等于某个固定值时很容易犯这个错。第三个坑是浮点数直接比较相等。用0.1 0.2 0.3判断结果永远是false。因为浮点数在内存里是二进制近似存储0.1加0.2的结果实际上是0.30000000000000004。这种比较需要先求差的绝对值再用一个极小值来判断是否近似相等比如Math.abs(a - b) 0.0000001。第四个坑是字符串比较用错了方式。判断str yes可能有时候结果是对的有时候是错的因为比较的是引用地址而不是内容。正确写法是yes.equals(str)。第五个坑是Scanner输入换行问题。如果先读取一个整数再读取一行字符串往往会发现字符串读取不到直接跳过了。因为nextInt()只读取了数字把换行符留在了缓冲区紧接着的nextLine()读到的就是那个换行符。解决办法是在读整数后再调用一次nextLine()把换行符消费掉或者统一用nextLine()读取后用Integer.parseInt()转成整数。第六个坑是switch的case穿透。我前面提过在写传统switch时容易漏掉break导致输出比预期多几行。排查时看一眼是不是所有case都带了break如果多个case故意要合并要注释说明避免后续维护的人误判。5.2 用IDEA调试器跟踪循环写复杂循环时我强烈建议用IDE的调试功能不要靠脑子和println推演。IDEA里调试很简单在代码行号旁边点一下打上一个红色断点然后点右上角的“虫子”按钮启动调试模式。程序会在断点处暂停下方会弹出Debug窗口显示当前所有变量的值。Debug窗口最常用的几个操作Step Over快捷键F8逐行执行但不会进入方法内部Step Into快捷键F7进入方法内部逐行执行Resume Program快捷键F9继续运行到下一个断点或者程序结束。我在跟踪for循环时会把断点打在循环体内部的第一行然后一遍一遍按F8观察变量i的变化。比如九九乘法表我看了外层i等于3时内层j是怎么从1变到3的循环退出条件是什么时候触发的。变量窗口里的值一目了然比自己在纸上推演更直观。IDEA还有一个值得用的功能是条件断点。右键断点输入i 5这种表达式程序只有当i等于5的时候才会在这个断点处暂停。这种断点适合调试那些“前几次正常、后面突然出错”的循环。5.3 常见问题速查表我把当天遇到的问题整理成一张对照表后面再写循环就容易自查了现象可能原因排查方法程序卡住不动、CPU飙高死循环条件永远成立打印循环变量看它是否在正常变化输出的内容比预期多switch漏写break或循环条件边界太大检查switch每个case检查循环终止条件循环一次都不执行while/for条件初始就不成立确认初始化变量和条件边界字符串判断结果不稳定用了而不是equals()改成内容.equals(变量)浮点数判断相等失败浮点精度问题使用差值绝对值判断多个if条件都执行了多个if并列而不是else if条件重叠想表达多选一就用else if数字拆位后结果对不上取模和整除的顺序混了用草稿纸先算一遍%和/的结果这张表现在看都是非常基础的内容但当时帮了我大忙。遇到问题先对着查一遍往往比自己瞎改快得多。6. 学习笔记的收尾一些经验如果把Day03的知识点画成一棵小树if/switch是树枝分叉的部分for/while/do-while是反复开花的部分break/continue是修剪枝叶的手。这棵小树种下之后后面学数组、字符串、集合都要在这棵树上继续长新枝。6.1 流程控制与后续内容的衔接学完Day03的当天晚上我尝试预习了一下数组的代码发现数组和for循环几乎是一对固定组合用for遍历数组元素、用循环给数组赋值、用循环累加数组求和。没有流程控制的底子数组根本玩不转。再往后学的String处理、集合遍历、IO读取本质也都是“循环 条件 数据结构”的组合。尤其是集合里的增强for循环和Iterator迭代器底层思想都是循环写业务代码时最常用的“遍历集合找某个元素”就是一个if判断放在循环体里。可以说Day03的分支循环是我编程生涯里复用率最高的知识没有之一。6.2 练习量建议与学习方法我的结论是第三天的内容值得比前两天花更多时间练习至少每个知识点写一遍代码。if系列各写一个例子、switch写两个例子、三个循环各写两个对比用例、break/continue/return各写一个触发场景然后做三个综合练习题。这大概需要花一个下午加一个晚上比盯着视频看三小时有效得多。练习时有几个小习惯我从第一天坚持到现在第一代码永远自己敲不复制粘贴哪怕是照着视频敲也要一个字一个字打进去第二每写完一个小程序先自己想一下“如果条件反过来输出会变成什么样”再实际改代码验证第三把运行结果随手截个图或者写在笔记里后期复习会很有用。第三天的笔记写到这已经很长了。我个人的体会是流程控制是整个JavaSE里“最不需要背、但最需要练”的内容。语法规则十分钟就能讲完但能不能在写题时快速想出循环结构和判断条件只能靠亲手敲代码来积累。这一天我敲的代码不算多大概两三百行但每一行都亲手跑过踩过的坑也都记了下来。写代码这事没有捷径多敲几遍自然就熟了。
返回列表