
写for循环的文章很多但大多要么太浅读完只会照着敲要么太深一上来就讲什么循环不变量、编译器优化新手直接劝退。我写Java这些年大大小小的项目都用for循环处理过数据踩过的坑也不少今天这篇就把for循环从最基础的语法到实战案例完整拆一遍顺便聊聊那些文档里不会明说、但面试和工作里一定会遇到的细节。这篇内容适合刚学Java、准备校招面试或者写了两年代码但对循环边界还不太有把握的朋友。Java的for循环本质上就是一个“重复执行某段代码直到条件不再满足”的控制结构。不管是遍历数组、处理集合、写冒泡排序还是算斐波那契数列for循环都是绕不开的基础功。我会先从语法和执行流程讲起再做几个可以跟着敲的实战案例接着把break、continue、return这些循环控制的关键词掰开揉碎最后结合面试高频题和排序算法收尾。全程用通俗的大白话配合可以直接运行的代码示例保证你能看懂、能复现、能举一反三。1. for循环的基础语法与执行流程1.1 语法结构与三个关键部分先看最经典的结构for (初始化语句; 条件表达式; 更新语句) { // 循环体 }括号里由分号隔开的三个部分是整个for循环的核心。初始化语句在循环开始前执行一次通常用来声明循环变量比如int i 0条件表达式在每次循环体执行之前都会判断只要结果为true就继续执行为false就退出更新语句在每次循环体执行完之后运行常见操作是i或i 2。举个例子for (int i 1; i 5; i) { System.out.println(第 i 次循环); }这个循环会输出5行。int i 1是起点i 5是继续条件i是每次加1。三个部分配合循环正好执行5次。这里有个小细节初始化语句声明的变量作用域仅限于这个for循环内部循环结束后变量就访问不到了。如果你在循环之后还想用这个变量就得在循环外面声明比如int i 0; for (; i 10; i) { // do something } // 这里还可以使用i新手不用纠结作用域问题但写代码的时候要明白这个区别因为后面学方法、学作用域的时候会反复遇到。1.2 执行流程从初始化到条件判断要真正理解for循环光看语法不够得走一遍完整的执行流程。Java的for循环执行顺序是这样的执行初始化语句且只执行这一次。判断条件表达式得到布尔值。如果条件是true执行循环体。循环体执行完毕执行更新语句。回到第2步重新判断条件。这个流程里最容易犯迷糊的是第三步和第四步的顺序很多人以为更新语句是在循环体之前执行的其实是在循环体之后。也就是说第一次循环时更新语句完全不参与直接由初始化语句把变量设置好再判断条件。我用一个具体例子帮你走一遍for (int i 0; i 3; i) { System.out.println(i i); }执行过程是i0判断0 3成立输出i 0然后执行i变成1再判断1 3成立输出i 1执行i变成2再判断2 3成立输出i 2执行i变成3再判断3 3不成立循环终止。i 0 i 1 i 2写代码的时候把这条执行链路印在脑子里很多边界问题都能避免。比如你想循环10次for (int i 0; i 10; i)和for (int i 1; i 10; i)效果一样但前者是程序员更常用的写法因为数组下标从0开始写成i 数组长度可以直接配合数组访问不容易出偏移错误。2. 实战案例从数组求和到双层循环2.1 从零实现一个数组求和理论说得再多不如实际敲一段代码。我们先用for循环解决一个最常见的需求求一个整型数组中所有元素的和。public class ArraySum { public static void main(String[] args) { int[] numbers {3, 8, 12, 5, 42}; int sum 0; for (int i 0; i numbers.length; i) { sum numbers[i]; } System.out.println(数组元素总和是 sum); } }这段代码的核心是sum numbers[i]等价于sum sum numbers[i]。我们用一个累加变量sum每轮循环把当前元素加进去循环结束后sum里就是全部元素的加和。注意循环条件写的是i numbers.length而不是i numbers.length。数组长度为5下标范围是0到4如果写成当i 5时访问numbers[5]就会抛出ArrayIndexOutOfBoundsException这是新手最容易踩的坑之一。从累加我们可以自然地扩展出另两个常见需求找最大值和求平均值。找最大值的基本思路是先假设第一个元素就是最大值然后从第二个元素开始逐个比较int max numbers[0]; for (int i 1; i numbers.length; i) { if (numbers[i] max) { max numbers[i]; } } System.out.println(最大值是 max);求平均值就简单了先求和再除以长度注意要用double接收结果否则整数相除会丢失小数部分。2.2 双层循环打印九九乘法表与矩阵处理单层循环只处理一维的数据遇到二维的场景——比如打印九九乘法表、遍历矩阵、处理表格数据——就需要嵌套使用for循环也就是双层循环。先看打印九九乘法表for (int i 1; i 9; i) { for (int j 1; j i; j) { System.out.print(j x i (i * j) \t); } System.out.println(); // 换行 }这里外层循环控制行数从1到9内层循环控制每一行输出多少列条件是j i也就是说第一行1列第二行2列第九行9列。内层循环里用\t制表符对齐看起来就整齐了。这个例子很好地说明了双层循环的执行逻辑外层循环每走一次内层循环会完整地走一轮。外层第一次i1时内层从j1到j1只运行一次然后换行外层第二次i2时内层从j1到j2运行两次再换行。循环次数是123...945次。同样的思路可以用来处理二维数组也就是矩阵。如果有一个3行4列的矩阵想计算所有元素的和可以这样写int[][] matrix { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }; int total 0; for (int row 0; row matrix.length; row) { for (int col 0; col matrix[row].length; col) { total matrix[row][col]; } } System.out.println(矩阵元素总和 total);外层循环遍历每一行matrix.length是行数3内层循环遍历当前行的每一列matrix[row].length是当前行的列数4。这种嵌套循环在处理二维数据时非常基础后面学矩阵运算、图像像素处理都会用上。3. 循环控制break、continue与return3.1 break、continue、return的行为差异for循环本身只会顺着执行但实际业务里我们经常需要提前终止循环、跳过某些元素或者在循环里直接结束整个方法。这时候就要用到三个关键词break、continue和return。它们看起来都很简单但行为有本质区别。break的作用是立即跳出当前所在的整个循环循环体后面的代码不再执行循环直接终止。比如for (int i 0; i 10; i) { if (i 5) { break; } System.out.println(i); }这段代码只会输出0到4当i5时触发break整个for循环结束后面即使条件还满足也不再执行了。break常用于在循环里查找某个元素找到了就不必继续浪费时间。continue的作用是跳过当前这一次循环继续执行下一次循环不会终止整个循环。比如for (int i 0; i 10; i) { if (i % 2 0) { continue; // 跳过偶数 } System.out.println(i); }这段代码输出1、3、5、7、9。当i是偶数时continue会跳过System.out.println(i)直接进入下一次循环也就是执行更新语句i。return更重一些它直接结束当前方法方法里所有代码都不再继续。如果return后面带值还会把值返回给调用方。举个例子public static void testReturn() { for (int i 0; i 10; i) { if (i 3) { return; } System.out.println(i); } System.out.println(循环结束了); }这个方法的输出是0、1、2然后整个方法就结束了最后那句“循环结束了”永远不会被执行。return实质上已经超出了循环控制的范畴它控制的是方法级别的退出。我用一个表格把三者的区别整理清楚关键词作用范围行为表现使用场景break循环退出当前循环找到目标元素后停止continue循环跳过本次迭代继续下一次过滤不需要的元素return方法结束整个方法循环中遇到异常或结果已确定3.2 带标签的break与continue前面说break和continue都是作用在“当前所在的循环”上。那如果我在双层循环的内层循环里想跳出外层循环该怎么办Java提供了带标签的break和continue也就是给循环起个名字然后用break 标签或continue 标签精确定位要跳过哪一层。outer: for (int i 0; i 5; i) { for (int j 0; j 5; j) { if (i 2 j 2) { break outer; // 跳出外层循环 } System.out.println(i i , j j); } } System.out.println(循环结束);这里的标签outer加在外层for循环前面内层遇到break outer时会直接跳出带outer标签的那一层循环相当于两层循环全部终止。带标签的continue也类似但它不是跳出外层循环而是跳到外层循环的更新语句那里继续外层循环的下一次迭代。这个语法用的频率不高但在处理嵌套循环的复杂业务时比用一个布尔变量去标记“是否跳出”要干净得多。给新手一个建议标签循环虽然强大但尽量少用因为可读性不好。如果代码里出现了两层以上的嵌套循环加标签跳转通常说明你的逻辑可以拆成独立的方法来优化了。4. 循环性能、常见陷阱与实战避坑4.1 性能优化从循环体到算法选择for循环的性能问题是面试里经常被问到的。很多人的第一反应是“少用for循环”或者“用lambda替换for循环”其实这都不准确。for循环本身的性能开销非常小真正的差距在于循环体内部的算法和数据结构选择。最经典的优化点是不要在循环体里重复计算数组长度。比如下面的写法for (int i 0; i list.size(); i) { // 业务操作 }每一次循环都会调用一次list.size()虽然size()方法本身很快但如果循环次数是百万级别重复调用的开销就会被放大。更稳妥的写法是把它提取到循环外面int size list.size(); for (int i 0; i size; i) { // 业务操作 }另一个常见优化是能一次遍历解决的事情不要写多个for循环。比如同时求数组中的最大值和最小值很多人会写两个循环各跑一遍其实一个循环就够了int max numbers[0]; int min numbers[0]; for (int i 1; i numbers.length; i) { if (numbers[i] max) { max numbers[i]; } if (numbers[i] min) { min numbers[i]; } }还有一点要特别注意for循环的优化不等于盲目地缩短代码。有时候为了缩短代码用复杂的条件表达式反而会降低代码的可读性和维护性。性能优化要建立在“确实有性能问题”的基础上不要为了优化而优化。4.2 常见陷阱边界问题、空指针与死循环我见过的for循环出问题的情况十有八九是边界条件没有理清楚。除了前面说的数组下标越界还有几个特别容易踩的坑。第一个坑是for (int i 0; i list.size(); i)当i等于list.size()时list.get(i)会抛越界异常。建议统一使用i list.size()这样更安全。第二个坑是空指针。如果循环遍历的对象本身就是null那在循环体里访问对象属性时就会抛出NullPointerException。比较稳妥的做法是在循环之前判断一下if (list null) { return; }第三个坑是死循环也就是条件永远为真循环永远不会退出。最常见的写法是忘记在循环里更新循环变量for (int i 0; i 10; ) { System.out.println(i); }这段代码会无限输出0因为i始终没有变化条件i 10永远成立。解决办法是把更新语句写进循环体里比如i。还有一个容易忽略的场景更新语句被continue跳过了。看这个例子for (int i 0; i 10; i) { if (i 5) { continue; } System.out.println(i); }这里的continue会跳过i吗不会。因为i是更新语句在循环体的末尾执行而continue只是跳过循环体剩下的部分更新语句依然会执行。但如果你把更新语句写在循环体内部的末尾就危险了int i 0; while (i 10) { if (i 5) { continue; } System.out.println(i); i; }这段代码在i5时触发continue它跳过了后面的System.out.println(i); i;导致i永远停在5形成死循环。for循环结构本身帮我们规避了这种问题但如果你把for循环改写成while循环就一定要注意更新语句不能放在可能被continue跳过的位置。5. 实战进阶排序算法与常见面试题5.1 冒泡排序与选择排序的for循环实现排序算法是面试里的老熟人而冒泡排序和选择排序是两种最适合用for循环演示的经典算法。理解了这两个排序你对for循环的嵌套使用、边界控制都会有一个质的提升。先看冒泡排序。基本思路是循环比较相邻的两个元素如果顺序不对就交换每一轮循环可以把当前范围内的最大元素“冒泡”到末尾。实现代码如下public static void bubbleSort(int[] arr) { int n arr.length; for (int i 0; i n - 1; i) { for (int j 0; j n - 1 - i; j) { if (arr[j] arr[j 1]) { int temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp; } } } }外层循环的i n - 1表示一共需要n-1轮比较内层循环的j n - 1 - i是关键因为每一轮都会确定一个最大元素放到末尾下一轮就不需要再比较已经排好位置的元素所以范围会逐渐缩小。选择排序的思路则不同每一轮选择出剩余元素中的最小值放到当前范围的最前面。public static void selectionSort(int[] arr) { int n arr.length; for (int i 0; i n - 1; i) { int minIndex i; for (int j i 1; j n; j) { if (arr[j] arr[minIndex]) { minIndex j; } } if (minIndex ! i) { int temp arr[i]; arr[i] arr[minIndex]; arr[minIndex] temp; } } }外层循环的i表示当前要确定的位置内层循环从i 1开始找最小值的位置。这两种排序的时间复杂度都是O(n²)在数据量大的时候性能不好但对于学习for循环和算法思维来说非常合适。5.2 斐波那契数列与素数求和的循环解法除了排序斐波那契数列和素数求和也是新手必练的经典题目它们都非常适合用for循环来实现。斐波那契数列的定义是第一项和第二项都是1从第三项开始每一项等于前两项之和。我们用for循环依次计算每一项int n 10; long a 1, b 1; System.out.print(a b ); for (int i 3; i n; i) { long next a b; System.out.print(next ); a b; b next; }这段代码的精髓在于变量更新计算完下一项后把原来的b赋给a把新的next赋给b这样下一次循环就能继续累加。如果不用临时变量直接a b; b a b你会发现结果完全错误因为第二行用的已经是改变后的a了。这也是新手经常踩的一个坑。再来看素数求和。判断一个数是不是素数需要从2开始尝试到它的平方根看能不能整除。结合for循环可以找出1到100之间所有素数并求和int sum 0; for (int num 2; num 100; num) { boolean isPrime true; for (int i 2; i Math.sqrt(num); i) { if (num % i 0) { isPrime false; break; } } if (isPrime) { System.out.print(num ); sum num; } } System.out.println(\n1到100的素数之和 sum);内层循环只需要到Math.sqrt(num)就够了因为如果一个数有大于平方根的因数那必然还有一个小于平方根的因数。这个优化虽然简单但在数据量大的时候能明显减少计算次数。循环找到非素数后立即break也是性能优化的小技巧。提到面试题for循环还有个高频场景是“循环单链表”。单链表不像数组那样能直接按下标访问但用for循环配合一个游标节点可以轻松遍历整个链表for (ListNode cur head; cur ! null; cur cur.next) { System.out.println(cur.val); }这里的“循环变量”是一个节点引用更新语句是cur cur.next每次沿着链表往后移动一个节点。用for循环写出来比用while循环看起来更加紧凑清晰面试的时候能给你加一点印象分。6. 写在最后我的一些实战体会写for循环这么多年我的一个体会是真正见功力的地方不在于你会不会写for (int i 0; i n; i)而在于你能不能快速判断边界条件在哪里、循环变量应该怎么更新、什么时候该用break和continue。这些能力靠看不出来必须一行一行地写、一个坑一个坑地踩才能形成肌肉记忆。如果你刚开始学Java我建议你找一本算法题集或者刷题网站从最基础的数组求和、反转数组、找最大值开始每天练几道for循环题直到不用想就能写对边界条件为止。等你做到这一步再去看冒泡排序、选择排序、矩阵转置这些经典题目就会发现它们都只是for循环的组合运用而已。最后再分享一个小技巧写for循环的时候先在注释里写下“循环变量从哪里开始、到哪里结束、每一步怎么变”再写代码。这个方法能让你在写嵌套循环时少掉一半的头发。希望这篇内容对你有用如果有哪里没讲透欢迎在评论区交流。