1. 循环到底在练什么:从一道题看透Java循环的底层逻辑
很多人学Java循环的时候,第一反应是“for、while、do-while,语法我都懂”,但一到写题就卡壳。这其实不是语法问题,而是没有建立起“循环思维”。所谓循环思维,说白了就是把一个重复动作抽象成“起点、终点、步长、重复体”四个要素。你拿到任何一道循环练习题,先别急着写代码,先问自己四个问题:从哪个数开始?到哪个数结束?每次变化多少?每次重复做什么?把这四个问题回答清楚,代码基本就出来了。
我见过太多初学者,拿到“打印九九乘法表”这种题,直接开始敲for,结果写到一半发现内层循环的边界条件搞不清楚。问题出在哪?出在他没有先把“行”和“列”的关系用自然语言描述出来。九九乘法表一共九行,第i行有i个表达式,每个表达式是j × i = 结果,其中j从1到i。你看,用自然语言把逻辑理清楚之后,代码就是翻译工作,而不是“猜”工作。
这篇内容适合谁看?如果你正在学Java基础,循环这块总是似懂非懂,做题靠背答案,换个题型就不会了,那这篇就是写给你的。如果你已经工作了一两年,想回头把基础打扎实,或者准备面试时被问到循环相关的算法题,这篇也能帮你重新梳理一遍。我会从最基础的累加求和开始,一路讲到嵌套循环、循环控制、常见陷阱,每道题都附上完整代码和详细解析,你可以直接拿去练。
提示:循环练习题的价值不在于“做出来”,而在于“为什么这样写”。每道题我都会先讲思路,再给代码,最后分析容易踩的坑。你最好先自己写一遍,再看我的解法。
2. 基础循环练习题:从累加到阶乘,把单层循环吃透
2.1 累加求和:最基础但最容易写错的入门题
题目:计算1到100所有整数的和。
这道题几乎是所有Java教材的第一道循环题。思路很简单:定义一个变量sum初始化为0,然后用for循环从1遍历到100,每次把当前数加到sum里。代码长这样:
int sum = 0; for (int i = 1; i <= 100; i++) { sum += i; } System.out.println("1到100的和是:" + sum);输出结果是5050。这道题太简单了对吧?但我要说的是,很多人在这里会犯一个低级错误:把sum定义在循环内部。如果你写成:
for (int i = 1; i <= 100; i++) { int sum = 0; sum += i; }那每次循环sum都会被重新赋值为0,最后的结果就是100,而不是5050。这个错误的本质是没理解“累加器变量必须定义在循环外部”。你可以把sum想象成一个存钱罐,循环就是每次往里面扔硬币,存钱罐必须放在外面,不能每次扔之前都换一个新的。
还有一个变体:计算1到100之间所有偶数的和。这时候你有两种写法。第一种是在循环体内加判断:
int sum = 0; for (int i = 1; i <= 100; i++) { if (i % 2 == 0) { sum += i; } }第二种是直接调整步长:
int sum = 0; for (int i = 2; i <= 100; i += 2) { sum += i; }两种写法结果都是2550。第一种更通用,第二种效率略高。实际开发中,如果步长固定,用第二种更简洁;如果条件复杂,用第一种更灵活。
2.2 求阶乘:注意数据类型的溢出陷阱
题目:计算10的阶乘(10!)。
阶乘的定义是n! = 1 × 2 × 3 × ... × n。思路和累加几乎一样,只是把加法换成乘法,初始值从0换成1。代码:
long result = 1; for (int i = 1; i <= 10; i++) { result *= i; } System.out.println("10的阶乘是:" + result);结果是3628800。这里我特意用了long而不是int,为什么?因为int的最大值是2147483647,而13的阶乘就已经超过这个范围了。你可以试试把上面的代码改成int result = 1,然后计算13的阶乘,看看会发生什么。你会得到一个负数,这就是整数溢出。溢出的原理是:当计算结果超过数据类型的最大值时,会从最小值重新开始“绕回来”,就像时钟从12点走到1点一样。
注意:做阶乘、累乘这类题目时,一定要预估结果的范围。如果结果可能超过
int的范围,就用long;如果long也不够,就要用BigInteger。这是很多初学者容易忽略的点。
2.3 统计位数:用循环拆解数字的每一位
题目:给定一个整数,统计它是几位数。比如12345是5位数。
这道题的核心思路是:每次把数字除以10,位数加1,直到数字变成0。代码:
int num = 12345; int count = 0; while (num != 0) { num /= 10; count++; } System.out.println("这是一个" + count + "位数");注意这里用了while而不是for,因为循环次数不确定,取决于数字的大小。while适合“不知道循环多少次,只知道循环条件”的场景。这道题还有一个变体:把数字的每一位打印出来。思路类似,只是每次取余数:
int num = 12345; while (num != 0) { int digit = num % 10; System.out.println(digit); num /= 10; }这样打印出来是5、4、3、2、1,顺序是反的。如果要正序打印,可以先把每一位存到数组里,再反向遍历。这个变体经常出现在面试题里,考察你对循环和数组的综合运用。
3. 嵌套循环:从打印图形到九九乘法表
3.1 打印直角三角形:理解外层控制行、内层控制列
题目:打印一个5行的直角三角形,每行星号数量递增。
* ** *** **** *****思路:外层循环控制行数,从1到5;内层循环控制每行的星号数量,从1到当前行号。代码:
for (int i = 1; i <= 5; i++) { for (int j = 1; j <= i; j++) { System.out.print("*"); } System.out.println(); }这里的关键是System.out.print和System.out.println的区别。print不换行,println换行。内层循环用print把星号打在同一行,内层循环结束后用println换行。很多初学者会把内层的print写成println,结果星号竖着排成一列。
如果你理解了这道题,可以试试打印倒直角三角形、等腰三角形、菱形。倒直角三角形只需要把内层循环的条件改成j <= 6 - i。等腰三角形稍微复杂一点,需要先打印空格再打印星号,空格数量是5 - i,星号数量是2 * i - 1。菱形则是两个等腰三角形拼在一起。这些题目看起来花哨,但本质都是“外层控制行,内层控制列”的嵌套逻辑。
3.2 九九乘法表:嵌套循环的经典综合题
题目:打印九九乘法表。
1×1=1 1×2=2 2×2=4 1×3=3 2×3=6 3×3=9 ...思路:外层循环i从1到9,代表行号;内层循环j从1到i,代表每行的表达式。每个表达式是j × i = j*i。代码:
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(); }这里用\t制表符对齐输出,让结果更整齐。如果你用空格,列会对不齐。这道题还有一个变体:打印完整的乘法表(不限制j <= i),也就是一个9×9的矩阵。只需要把内层循环改成j <= 9即可。
提示:嵌套循环的总执行次数是外层次数乘以内层次数。九九乘法表的外层执行9次,内层平均执行5次,总共执行45次。理解这一点对后面分析算法效率很重要。
3.3 打印素数:嵌套循环加标志位的组合应用
题目:打印1到100之间的所有素数。
素数的定义是只能被1和自身整除的数。判断一个数n是否为素数,需要检查从2到n-1之间有没有能整除它的数。代码:
for (int i = 2; i <= 100; i++) { boolean isPrime = true; for (int j = 2; j < i; j++) { if (i % j == 0) { isPrime = false; break; } } if (isPrime) { System.out.print(i + " "); } }这里引入了两个新东西:boolean标志位和break。标志位isPrime初始为true,一旦发现能整除的数就设为false并跳出内层循环。外层循环结束后检查标志位,如果还是true就打印。这个模式在循环练习题里非常常见,你一定要掌握。
优化点:内层循环其实不需要检查到n-1,只需要检查到sqrt(n)即可。因为如果n有一个大于sqrt(n)的因子,那必然有一个小于sqrt(n)的对应因子。比如100的因子对是(2,50)、(4,25)、(5,20)、(10,10),你只需要检查到10就能判断100不是素数。这个优化能把内层循环的次数从n降到sqrt(n),效率提升非常明显。
4. 循环控制与常见陷阱:break、continue和死循环
4.1 break和continue的区别:一个跳出,一个跳过
break的作用是立即终止当前循环,继续执行循环后面的代码。continue的作用是跳过本次循环剩余的代码,直接进入下一次循环。用一道题来区分:
题目:打印1到10之间的所有数,但遇到5就停止。
for (int i = 1; i <= 10; i++) { if (i == 5) { break; } System.out.print(i + " "); } // 输出:1 2 3 4题目:打印1到10之间的所有数,但跳过5。
for (int i = 1; i <= 10; i++) { if (i == 5) { continue; } System.out.print(i + " "); } // 输出:1 2 3 4 6 7 8 9 10你看,break是“不玩了”,continue是“这次不算,下次继续”。在嵌套循环中,break和continue默认只作用于当前所在的那一层循环。如果你想跳出外层循环,需要给外层循环加标签:
outer: for (int i = 1; i <= 3; i++) { for (int j = 1; j <= 3; j++) { if (i == 2 && j == 2) { break outer; } System.out.println(i + "," + j); } }标签语法在实际开发中很少用,但面试题里偶尔会出现,了解一下就行。
4.2 死循环:while(true)的正确打开方式
死循环是指循环条件永远为true,循环体会一直执行下去。最常见的死循环写法是while(true)。很多人觉得死循环是bug,其实不然,死循环在特定场景下是故意设计的。比如游戏的主循环、服务器的监听循环,都需要一直运行。关键是要在循环体内设置退出条件:
while (true) { // 做一些事情 if (退出条件) { break; } }初学者最容易犯的死循环错误是忘记更新循环变量。比如:
int i = 1; while (i <= 10) { System.out.println(i); // 忘记写 i++ }这段代码会无限打印1。因为i永远是1,条件永远成立。避免这种错误的方法是在写while循环时,先把循环变量的更新语句写好,再写循环体。
4.3 do-while:至少执行一次的循环
do-while和while的区别是:do-while先执行一次循环体,再判断条件。也就是说,do-while的循环体至少执行一次。典型场景是菜单选择:
Scanner scanner = new Scanner(System.in); int choice; do { System.out.println("1. 开始游戏"); System.out.println("2. 退出"); System.out.print("请选择:"); choice = scanner.nextInt(); } while (choice != 2);不管用户输入什么,菜单至少显示一次。如果用while,就需要先手动显示一次菜单,代码会重复。do-while的适用场景就是“先做一次,再决定要不要继续”。
5. 循环练习题速查表与避坑指南
5.1 常见题型与解题思路对照表
| 题型 | 推荐循环 | 核心思路 | 易错点 |
|---|---|---|---|
| 累加求和 | for | 累加器定义在循环外 | 累加器定义在循环内 |
| 求阶乘 | for | 初始值为1,注意溢出 | 用int导致溢出 |
| 统计位数 | while | 每次除以10 | 忘记更新循环变量 |
| 打印图形 | 嵌套for | 外层行,内层列 | print和println混用 |
| 九九乘法表 | 嵌套for | 内层边界是i | 内层边界写成9 |
| 判断素数 | 嵌套for | 标志位+break | 忘记重置标志位 |
| 菜单选择 | do-while | 至少执行一次 | 用while导致代码重复 |
5.2 循环练习中的五个高频坑
第一个坑:循环变量在循环外定义。比如int i; for (i = 0; i < 10; i++),这种写法虽然合法,但i的作用域会扩大到循环外,容易造成变量污染。推荐在for的初始化部分定义变量,把作用域限制在循环内。
第二个坑:浮点数作为循环变量。比如for (double d = 0.1; d <= 1.0; d += 0.1),由于浮点数精度问题,循环次数可能不是10次。如果必须用浮点数,建议用整数循环变量再除以倍数。
第三个坑:在循环体内修改循环变量。比如for (int i = 0; i < 10; i++) { i++; },这样循环只执行5次。除非你明确知道自己在做什么,否则不要在循环体内修改循环变量。
第四个坑:嵌套循环中混淆内外层变量。比如打印九九乘法表时,把j <= i写成i <= j,结果就完全不对了。写嵌套循环时,给变量起有意义的名字,比如row和col,比i和j更不容易搞混。
第五个坑:忘记break导致多余计算。比如判断素数时,发现一个因子就可以跳出内层循环了,如果不写break,内层循环会继续执行,浪费性能。虽然对100以内的数影响不大,但养成好习惯很重要。
提示:做循环练习题时,建议先在纸上画出循环的执行过程,特别是嵌套循环,画出每一轮内外层变量的值。这个过程看起来笨,但能帮你真正理解循环的执行流程。
5.3 从练习题到实际应用的过渡建议
循环练习题做完了,怎么过渡到实际开发?我的建议是找一些小项目来练手。比如写一个控制台版的猜数字游戏,用while循环让玩家一直猜直到猜对;写一个简单的学生成绩统计程序,用for循环遍历数组求平均分;写一个打印日历的程序,用嵌套循环处理月份和日期。这些项目不大,但能把循环和条件判断、数组、方法结合起来,比单纯做练习题更接近真实开发。
另外,LeetCode上有很多循环相关的简单题,比如“两数之和”的暴力解法、“罗马数字转整数”的遍历解法,都可以用循环来实现。做这些题的时候,不要只追求通过,要思考有没有更优的循环写法。比如能不能用增强for循环代替普通for循环?能不能用while代替for让代码更清晰?这些思考会让你对循环的理解更上一层楼。
我个人在实际教学中的体会是,循环这块最大的分水岭不是语法,而是“把问题拆解成重复步骤”的能力。有些人天生擅长这种拆解,有些人需要大量练习才能建立感觉。如果你属于后者,不要着急,把上面这些题反复写三遍,每遍都尝试不同的写法,慢慢你就会发现,看到一道新题时,脑子里会自动浮现出循环的框架。这个感觉一旦建立起来,后面学数组、集合、算法都会轻松很多。