
Java基础教程:流程控制与循环语句的常见用法
程序通常不是从第一行开始,一路执行到最后一行。根据条件选择不同操作、重复处理一批数据,都是日常开发中的基本需求。Java 使用条件语句、循环语句和跳转语句控制执行流程。理解它们的适用场景和边界,能避免许多常见问题,例如循环条件写错、整数溢出、误删集合元素,或把输入校验误当成安全保证。
本文示例以常见的 Java 语法为主。每段示例都包含可运行的类,保存为对应的 .java 文件后,可以使用 javac 文件名.java 编译,再使用 java 类名 运行。代码中的条件、循环和输入处理可以按实际程序调整。
一、条件表达式与 if 语句
if 根据布尔表达式的结果决定是否执行代码块。Java 的条件表达式必须是 boolean 类型,不能像某些语言一样把整数直接当作真或假。
public class IfExample {
public static void main(String[] args) {
int score = 86;
if (score >= 90) {
System.out.println("优秀");
} else if (score >= 60) {
System.out.println("及格");
} else {
System.out.println("不及格");
}
}
}
执行顺序是从上到下:第一个条件为真时,执行对应代码块,剩下的分支不再检查。因此,条件顺序会影响结果。比如成绩判断应先检查较高分数,再检查及格线。如果把 score >= 60 放在最前面,86 分会直接进入“及格”分支,永远到不了“优秀”分支。
1. 适合使用 if 的情况
- 根据一个或多个条件选择不同操作,例如权限检查、范围判断和状态处理。
- 条件之间存在先后关系,需要按顺序判断。
- 需要组合多个布尔条件,或在不同分支中执行不同的代码块。
比较运算符会产生布尔结果,例如 ==、!=、<、>=。逻辑运算符 && 表示逻辑与,|| 表示逻辑或,! 表示取反。
public class ConditionExample {
public static void main(String[] args) {
int age = 20;
boolean hasTicket = true;
if (age >= 18 && hasTicket) {
System.out.println("可以入场");
}
String name = null;
if (name == null || name.isBlank()) {
System.out.println("姓名为空");
}
}
}
&& 和 || 具有短路特性:当左侧已经足以确定整个表达式的结果时,右侧不会执行。例如 name == null || name.isBlank() 中,若 name 为 null,左侧为真,右侧不会调用 isBlank(),从而避免空指针异常。反过来,若使用单个 |,两侧都会计算,不具有短路行为。
不要混淆赋值符号 = 和相等比较符号 ==。引用类型的 == 比较的是两个引用是否指向同一个对象;比较字符串内容通常应使用 equals:
String role = "admin";
if ("admin".equals(role)) {
System.out.println("管理员");
}
将常量字符串放在前面,可以避免 role 为 null 时调用方法而导致空指针异常。对于可能为 null 的对象,应根据业务需要明确处理空值。
2. 大括号与边界条件
即使分支中只有一条语句,也建议使用大括号。省略大括号时,if 只控制紧随其后的单条语句,稍后维护代码时容易误判执行范围:
if (score >= 60)
System.out.println("及格");
System.out.println("完成");
上面只有第一条输出受 if 控制,“完成”会无条件输出。更稳妥的写法是:
if (score >= 60) {
System.out.println("及格");
System.out.println("完成");
}
范围判断还要仔细处理边界。例如有效年龄若定义为 0 到 120(包含两端),条件应写成 age >= 0 && age <= 120。小于还是小于等于,取决于需求定义,不应凭习惯猜测。
二、switch:按离散值选择分支
当一个表达式需要与多个固定值匹配时,switch 往往比连续的 if-else 更清楚,例如菜单选项、星期编号或有限的状态值。传统 switch 的分支通常使用 case,并以 break 结束:
public class SwitchExample {
public static void main(String[] args) {
int day = 6;
switch (day) {
case 1:
System.out.println("星期一");
break;
case 2:
System.out.println("星期二");
break;
case 3:
System.out.println("星期三");
break;
case 4:
System.out.println("星期四");
break;
case 5:
System.out.println("星期五");
break;
case 6:
case 7:
System.out.println("周末");
break;
default:
System.out.println("无效的星期编号");
}
}
}
多个 case 可以共用同一段处理逻辑。这里 6 和 7 都会进入“周末”分支。传统写法如果遗漏 break,程序会继续执行后续分支中的语句,这称为贯穿执行(fall-through)。有意贯穿时应让代码意图清晰;不需要贯穿时,每个分支通常都应显式结束。
default 处理没有匹配到的情况,可以省略,但对外部输入或可能出现未知值的情况,明确处理默认分支往往更易于维护。传统 switch 常用于整数、字符、枚举和字符串等类型;具体可用类型受 Java 版本和语言规则限制,不能将任意对象直接作为普通 case 值。
较新的 Java 版本还支持箭头形式的 switch 标签。它不会像传统标签那样自然贯穿到下一个分支,可减少遗漏 break 的问题。此语法需要使用支持它的 Java 版本,项目应以实际配置的编译器版本为准:
public class SwitchArrowExample {
public static void main(String[] args) {
int code = 2;
switch (code) {
case 1 -> System.out.println("新建");
case 2 -> System.out.println("处理中");
case 3 -> System.out.println("已完成");
default -> System.out.println("未知状态");
}
}
}
若只有少量简单分支,使用 if 并没有问题。选择 switch 还是 if,主要看表达的逻辑是否清楚;不要为了使用某种语法,把多个复杂条件强行改写成不易读的结构。
三、for 循环:适合已知重复次数的任务
传统 for 循环由初始化部分、继续条件和更新部分组成:
for (初始化; 继续条件; 更新) {
// 循环体
}
执行顺序是:先执行初始化;再判断条件;条件为真时执行循环体;循环体结束后执行更新;然后再次判断条件。条件为假时,循环结束。
public class ForExample {
public static void main(String[] args) {
for (int i = 1; i <= 5; i++) {
System.out.println(i);
}
}
}
这段程序输出 1 到 5。若改成 i < 5,则输出 1 到 4。循环次数和边界由条件共同决定,特别要确认起始值是否包含在结果中,以及终止值是否应该包含。
1. 遍历数组时处理下标
需要下标时,传统 for 循环很实用。例如,要按位置读取数组元素、只处理某个区间,或同时读取当前位置和下一个位置,都需要控制下标:
public class ArrayIndexExample {
public static void main(String[] args) {
int[] values = {10, 20, 30};
for (int i = 0; i < values.length; i++) {
System.out.println("下标=" + i + ",值=" + values[i]);
}
}
}
数组的合法下标范围是 0 到 length - 1。因此常见条件是 i < values.length,而不是 i <= values.length。后者会在最后一次尝试访问 values[values.length] 时导致 ArrayIndexOutOfBoundsException。
2. 更新表达式与整数溢出
循环变量通常每轮递增,但更新操作也可以是递减或其他变化。要确认每一轮都在朝终止条件前进:
for (int i = 10; i > 0; i--) {
System.out.println(i);
}
如果条件写成 i > 0,却在每轮将 i 加一,条件可能一直成立,形成意外的无限循环。若使用 int 计算特别大的计数,整数可能超出类型表示范围并发生溢出;Java 的整数溢出不会自动抛出异常。因此,涉及上限、累计值或外部输入时,要检查数据范围,必要时选用更合适的类型,并对溢出风险做显式处理。
四、增强 for:遍历元素而不关心下标
增强 for(也叫 for-each)适合依次访问数组或实现了 Iterable 的对象中的元素。若不需要下标,语法通常更简洁:
public class ForEachExample {
public static void main(String[] args) {
String[] names = {"Lin", "Mei", "Kai"};
for (String name : names) {
System.out.println(name);
}
}
}
也可以遍历集合:
import java.util.List;
public class ListLoopExample {
public static void main(String[] args) {
List<String> items = List.of("Java", "SQL", "HTTP");
for (String item : items) {
System.out.println(item);
}
}
}
List.of 在 Java 9 及以上可用,返回的列表不可修改。若项目使用较老版本,可用 Arrays.asList 或其他方式创建列表;这些方法的可修改性特征并不完全相同,替换时应查阅所用 Java 版本的 API 文档。
增强 for 的局限是没有直接提供下标。需要位置时,可以使用传统 for,或根据集合类型使用迭代器。遍历可变集合时,不要随意在增强 for 循环中通过集合的普通删除方法修改集合结构,这可能导致 ConcurrentModificationException。这不是线程并发才会触发的异常:单线程遍历时,以不符合迭代器规则的方式修改集合也可能触发。需要删除元素时,可以在合适场景使用迭代器的 remove 方法,或使用集合提供的筛选操作。
五、while 循环:条件满足时重复执行
while 先判断条件,再决定是否执行循环体。若条件一开始就为假,循环体一次也不会执行。它适合重复次数事先不确定、由运行状态决定结束时机的任务,例如读取数据直到没有更多输入:
public class WhileExample {
public static void main(String[] args) {
int number = 1;
while (number <= 5) {
System.out.println(number);
number++;
}
}
}
循环体中必须有使条件最终变为假的途径。常见错误是遗漏变量更新,导致条件一直为真:
int number = 1;
while (number <= 5) {
System.out.println(number);
// 忘记更新 number,循环无法按预期结束
}
遇到卡住的程序,应检查循环条件、循环变量更新位置,以及循环体是否可能提前跳过更新语句。循环内部存在 continue 时尤其要谨慎:如果更新语句写在循环体末尾,而执行流在更新前就跳到下一轮,变量可能不会变化。
六、do-while:至少执行一次
do-while 先运行循环体,再判断条件,因此循环体一定至少执行一次。它适合需要先显示一次菜单或先尝试一次操作,再根据结果决定是否继续的情况。
public class DoWhileExample {
public static void main(String[] args) {
int attempt = 1;
do {
System.out.println("尝试次数:" + attempt);
attempt++;
} while (attempt <= 3);
}
}
注意条件末尾必须有分号:while (条件);。这与 while 循环的语法不同。漏掉分号会造成编译错误;误把分号写在循环体前,则可能让实际循环体不受预期条件控制。
七、break 与 continue:改变循环中的执行路径
break 结束当前所在的循环,或结束传统 switch。continue 跳过本轮循环剩余部分,进入下一轮判断。以下示例寻找第一个符合条件的数值:
public class JumpExample {
public static void main(String[] args) {
int[] values = {3, 8, 12, 19};
for (int value : values) {
if (value % 2 == 0) {
System.out.println("第一个偶数:" + value);
break;
}
}
for (int i = 1; i <= 5; i++) {
if (i == 3) {
continue;
}
System.out.println(i);
}
}
}
第一个循环找到偶数 8 后通过 break 结束。第二个循环在 i 为 3 时跳过输出,其他数字照常输出。使用 continue 前,先确认循环变量仍会正确更新,避免产生无法结束的循环。
嵌套循环中,不带标签的 break 和 continue 只作用于最内层循环。如果需要跳出多层循环,可以使用带标签的 break,但应适量使用,因为过多标签跳转会让控制流程不易追踪:
public class LabeledBreakExample {
public static void main(String[] args) {
int[][] matrix = {
{1, 2, 3},
{4, 5, 6}
};
search:
for (int row = 0; row < matrix.length; row++) {
for (int col = 0; col < matrix[row].length; col++) {
if (matrix[row][col] == 5) {
System.out.println("找到:row=" + row + ", col=" + col);
break search;
}
}
}
}
}
八、循环中的输入处理与参数限制
程序从命令行、网络请求、文件或用户界面接收数据时,不能假设输入一定符合预期。流程控制可以用于校验数据,但校验规则应来自实际业务约束,不存在一个适用于所有应用的通用“安全长度”或“安全上限”。例如,某个字段的最大长度、数值区间和允许字符,都要由业务协议或数据模型确定。
下面的示例读取一个整数,并检查它是否位于人为设定的 1 到 100 范围内。这个范围仅用于演示,不代表 Java 或安全标准规定的通用限制。示例还使用 hasNextInt 检查输入是否能解析为整数,避免直接调用 nextInt 时遇到非整数输入而抛出输入不匹配异常:
import java.util.Scanner;
public class InputValidationExample {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入 1 到 100 的整数:");
if (!scanner.hasNextInt()) {
System.out.println("输入不是有效整数");
return;
}
int value = scanner.nextInt();
if (value < 1 || value > 100) {
System.out.println("数值超出允许范围");
return;
}
System.out.println("有效输入:" + value);
}
}
这只是单次输入校验示例。实际程序可以在循环中持续提示,直到收到有效值,或达到合理的尝试次数。是否重试、拒绝请求、记录错误或终止操作,应由产品和系统设计决定。若输入来自远端,验证应发生在可信边界上;客户端页面上的限制可以改善体验,但不能代替服务端校验。
范围校验也不等同于完整的安全防护。它不能自动防止注入、越权或资源耗尽等问题。对用户输入进行长度和格式限制,有助于约束数据,但具体用途和效果取决于系统设计。数据库操作应使用参数化查询,输出到网页时应按目标上下文进行编码;不能仅凭一个 if 判断,就认为输入已经安全。
此外,校验和后续使用之间如果数据可能被其他线程或其他操作修改,还需要考虑一致性问题。单纯先检查、稍后再使用,并不在所有场景下保证数据在使用时仍满足同一条件。应根据数据来源和并发模型选择合适的设计。
九、循环边界与常见错误清单
1. 少执行一次或多执行一次
处理固定数量的元素时,先写明有效范围。例如数组长度为 n,下标范围是 0 到 n - 1,遍历条件通常是 i < n。处理数字区间时则需要确认上下界是否包含,不能把 < 和 <= 当成可以随意替换的写法。
2. 条件更新方向不一致
如果循环从 0 递增到上限,条件通常随变量增长而逐渐变为假;如果变量递减,条件也要和递减方向匹配。逐项检查“初始值、继续条件、每轮更新”这三部分,往往比盲目调整代码更有效。
3. 用 == 比较不适合的对象
对象的 == 比较引用身份,不一定比较内容。字符串内容比较通常用 equals;可空字符串需先判空,或采用常量在前的调用形式。
4. switch 漏写 break
传统 switch 中,一个分支执行结束后可能继续落入后续分支。为每个不需要贯穿的分支添加 break,或在受支持的 Java 版本中使用箭头形式,并通过测试覆盖边界值和默认分支。
5. 遍历时修改集合
遍历集合时若需要删除或替换元素,先选择与集合类型相匹配的方式。不要仅凭“代码没有多线程”就认定修改集合一定安全;集合的迭代规则与线程安全是不同问题。
6. 用浮点数做精确边界判断
float 和 double 使用二进制浮点表示,某些十进制小数不能精确表示。对于需要精确货币计算或严格小数边界判断的场景,通常应考虑 BigDecimal,并明确舍入规则;普通循环计数则优先使用整数变量。
7. 把循环限制当成资源保护的全部
限制循环次数可以减少某些无界处理的风险,但实际资源消耗还取决于每轮执行的操作、数据规模、并发情况和外部依赖。对于文件、网络请求和批量任务,应结合系统容量设置合理上限、超时或取消机制,并处理失败情况。具体阈值应由需求和运行环境确定,不应照搬任意数字。
十、如何选择流程控制结构
- 条件分支有顺序、范围或复杂组合时,优先考虑
if-else。 - 一个值对应若干离散选项时,可以考虑
switch,并明确处理默认情况。 - 重复次数或遍历范围明确、且可能需要下标时,使用传统
for。 - 只需依次处理数组或可迭代集合中的元素时,使用增强
for。 - 结束时机由循环条件决定、开始前允许执行零次时,使用
while。 - 业务要求先执行一次,再判断是否重复时,使用
do-while。
写流程控制代码时,可以按一个简单顺序检查:条件是否有明确类型和含义;边界是否覆盖了需求中的最小值与最大值;循环是否有可靠的退出路径;数组和集合访问是否越界;分支遗漏时是否有合理处理;外部输入是否经过与业务规则相符的校验。掌握这些要点,比记住某种语法形式更重要。









