流程控制决定代码走向
流程控制看起来是最简单的语法:if、for、switch,大学课本一小时就能讲完。但真正写业务代码时,出问题的往往不是“不会写”,而是边界条件没想全:循环少跑一次、switch 漏了 break、数组下标多算了一位。这类 bug 的共同点是编译期完全正常,运行时才暴露。
这篇把分支、循环、数组放在一起讲,因为它们的坑几乎都集中在“边界”两个字上。
分支:if 与 switch
if 的重点是把条件写完整。判断字符串内容用 equals 而不是 ==,判断对象非空要把 null 放在前面;多个条件互斥时用 else if,别写成一串独立的 if,否则条件重叠时后面的分支也会执行。
switch 在 JDK 7 之后支持 String,JDK 14 之后又多了箭头语法。传统写法要记住三点:每个 case 结束加 break,default 不要省,case 的值必须是常量。很多人写 case "A" 时忘了 break,结果“优秀”下面又打印了一遍“良好”,这就是常说的 case 穿透。
String level = "A";
switch (level) {
case "A":
System.out.println("优秀");
break;
case "B":
System.out.println("良好");
break;
default:
System.out.println("未知等级");
}
如果确实想利用穿透(比如多个等级走同一段逻辑),建议在 case 后面写一行注释 // fall through,让读代码的人知道这是故意的。
循环:三种写法各有场景
for 适合次数确定的循环,while 适合“条件满足就一直做”,do-while 至少执行一次。日常业务里 90% 是 for 和增强 for。求和是最直观的例子:
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
System.out.println(sum); // 5050
增强 for 写起来更简洁,但它有两个限制:拿不到下标,遍历时不能删除集合元素。还有一个容易被忽略的点——循环变量只是元素的副本,改它不会改原数组。
break 和 continue 是最容易被忽略的两个关键字:前者跳出整个循环,后者结束本次、进入下一次。它们后面不要再写代码,编译器会直接报“不可达语句”。多层嵌套想一次跳出,可以用带标签的 break outer;,但更推荐把内层逻辑抽成方法,用 return 结束。
int[] arr = {1, 2, 3};
for (int x : arr) {
x = x * 2; // 改的是副本,数组内容不变
}
for (int i = 0; i < arr.length; i++) {
arr[i] = arr[i] * 2; // 这样才能修改数组
}
数组:长度固定,下标从 0 开始
数组一旦创建,长度就固定了。基本类型数组创建后元素有默认值(int 为 0,boolean 为 false,引用类型为 null),但局部变量本身不会自动初始化。
import java.util.Arrays;
public class ArrayDemo {
public static void main(String[] args) {
int[] scores = new int[3];
scores[0] = 90;
System.out.println(Arrays.toString(scores)); // [90, 0, 0]
String[] names = {"Amy", "Bob", "Cindy"};
for (String name : names) {
System.out.println(name);
}
int[][] grid = new int[2][3];
grid[1][2] = 7;
System.out.println(grid.length + "," + grid[0].length); // 2,3
}
}
二维数组可以理解成“数组的数组”:grid.length 是行数,grid[0].length 是列数。声明 int[][] grid = new int[2][]; 时只分配了行,列还是 null,直接访问 grid[0][0] 会空指针。
常见坑
1. 下标越界
长度为 3 的数组,合法下标是 0、1、2。循环条件写成 i <= arr.length 是最常见的越界原因,应该是 i < arr.length。报错信息 ArrayIndexOutOfBoundsException: 3 里的数字就是越界的下标,直接对着数字看就能定位。
2. 用 == 比较数组
arr1 == arr2 比较的是两个数组对象的地址,内容一样也是 false。比较内容用 Arrays.equals,二维数组用 Arrays.deepEquals。
3. switch 忘写 break
表现是“打印了不该打印的分支”。排查时先看报错分支的上下有没有 break,再看是不是故意穿透但没写注释。
4. 增强 for 里改元素
前面演示过,x = x * 2 改的是副本。需要修改数组内容,老老实实用普通 for 加下标;需要遍历时删除集合元素,用 Iterator.remove 或 removeIf。
5. 循环边界差一
从 1 加到 100 写成 i < 100 会少加一个 100;截取字符串时把 end 当成包含位置,会多截一位。写完循环用 0 个元素、1 个元素、刚好一个边界值各跑一次,能挡住大部分问题。
排查步骤
- 先看异常栈第一行的异常类名和“at 行号”,确定是哪个数组、哪次循环;
- 把
length、下标、循环条件打印出来,确认边界值; - 分支问题在关键
case前打日志,确认实际走到哪个分支; - 数组比较统一换成
Arrays.equals,不要凭肉眼对比输出; - 给这段逻辑补一个覆盖空数组、单元素、边界值的单元测试。
评论