专栏: JavaSE
系列导航: 一、数据类型 → 二、运算符 → 三、逻辑控制(本篇)
代码来源:20261018/src/Test.java
前两篇把变量、数据类型和运算符准备好了。但程序如果只会从上到下顺序执行,还称不上“会思考”。真正让程序根据条件做选择、把某段代码重复执行的,是逻辑控制。
这篇笔记结合我练习代码里的闰年判断、switch、阶乘、break/continue、Scanner 和猜数字游戏,把 Java 的逻辑控制串一遍。
说明:练习文件里用
main1到main8把示例分开,本文按知识点拆成独立片段。实际运行时,把对应代码放进main方法里即可。
逻辑控制到底在控制什么?
程序的基本执行结构可以分成三类:
| 结构 | 代表语句 | 作用 |
|---|---|---|
| 顺序结构 | 普通语句 | 从上到下依次执行 |
| 分支结构 | if、switch | 根据条件选择执行哪段代码 |
| 循环结构 | while、for、do-while | 条件满足时重复执行 |
除此之外,break、continue、return 可以改变循环或方法的执行流程,常和循环一起使用。
写逻辑控制时,最需要想清楚三件事:
- 边界在哪里:条件什么时候成立,什么时候不成立;
- 循环变量怎么变:每一轮有没有向结束条件靠近;
- 什么时候退出:是自然结束,还是
break、return提前结束。
if:让程序学会选择
if 是最基础的分支语句:
if (布尔表达式) {
// 条件成立时执行
} else if (布尔表达式) {
// 另一个条件成立时执行
} else {
// 以上条件都不成立时执行
}
几个要点:
if括号里的结果必须是boolean,不能写if (1);- 同一组
if / else if / else最多只会命中一个分支; - 多个彼此独立的
if是可以同时成立的,不要和else if混淆; - 即使循环体或分支里只有一行代码,也建议保留大括号,后期加代码时不容易出错;
else会和离它最近、还没有匹配的if结合,嵌套时尤其要注意。
例子:判断闰年
闰年的规则是:
- 普通年份:能被 4 整除的是闰年;
- 世纪年份:能被 100 整除时,还必须能被 400 整除才是闰年。
用嵌套 if 写出来:
int year = 2000;
if (year % 100 == 0) {
// 世纪年:还要继续判断能不能被 400 整除
if (year % 400 == 0) {
System.out.println("是闰年");
} else {
System.out.println("不是闰年");
}
} else {
// 普通年份:能被 4 整除就是闰年
if (year % 4 == 0) {
System.out.println("是闰年");
} else {
System.out.println("不是闰年");
}
}
这段代码的判断路径可以画成这样:
year % 100 == 0 ?
├─ 是 → year % 400 == 0 ? 是闰年 : 不是闰年
└─ 否 → year % 4 == 0 ? 是闰年 : 不是闰年
当 year = 2000 时,先判断 2000 % 100 == 0 成立,再判断 2000 % 400 == 0 也成立,所以输出“是闰年”。
熟悉之后,可以把条件合并成一行:
boolean isLeap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
System.out.println(isLeap ? "是闰年" : "不是闰年");
这里用到了上一节的逻辑运算符。括号不能省,因为 && 的优先级高于 ||。嵌套写法更适合初学者理解判断过程,合并写法更简洁,实际开发中可以根据可读性选择。
switch:等值判断的另一种写法
当判断的是“某个变量等于几个固定值中的哪一个”时,switch 会比一长串 else if 更清楚:
int a = 1;
switch (a) {
case 1:
System.out.println(1);
break;
case 2:
System.out.println(2);
break;
case 3:
System.out.println(3);
break;
default:
System.out.println("haha");
break;
}
当 a = 1 时,程序输出 1。
switch 有几个容易出错的规则:
- 支持的类型有限。 常见的是
byte、short、char、int,以及它们的包装类;此外还支持String和枚举。long、float、double、boolean不能作为switch的选择表达式。 case后面必须是常量。 不能写case a > 1:,也不能写运行时才能确定的值。break用来结束当前分支。 如果漏写,程序会继续执行下一个case的代码,这叫“贯穿”(fall through)。default不是必须的,但建议保留。 它负责处理所有case都没匹配上的情况,位置可以放在任意位置,通常放在最后。- 多个
case可以共用一段逻辑:
case 1:
case 2:
System.out.println("1 或 2");
break;
如果使用的是 Java 14 及以上版本,还可以写成箭头形式,不需要 break,也不会发生贯穿:
switch (a) {
case 1 -> System.out.println(1);
case 2 -> System.out.println(2);
default -> System.out.println("haha");
}
初学阶段先把传统的 case/break 写熟,再了解箭头语法即可。
while:条件成立就继续
while 的语法很简单:
while (布尔表达式) {
// 循环体
}
它的执行顺序是:先判断条件,条件为 true 就执行循环体,执行完再回来判断,直到条件为 false。
写 while 时一定要保证循环变量会变化,否则很容易写出死循环。
例子:计算 5 的阶乘
int n = 1;
int result = 1;
while (n <= 5) {
result *= n;
n++;
}
System.out.println(result); // 120
这里的三要素是:
- 初值:
n = 1; - 条件:
n <= 5; - 更新:每轮执行
n++。
如果漏掉 n++,n 永远是 1,循环就停不下来了。
例子:计算 1! + 2! + 3! + 4! + 5!
这个例子用到了嵌套循环:
int num = 1;
int sum = 0;
while (num <= 5) {
int tmp = 1;
int factorResult = 1;
// 里层循环:计算 num 的阶乘
while (tmp <= num) {
factorResult *= tmp;
tmp++;
}
// 外层循环:把每个阶乘累加
sum += factorResult;
num++;
}
System.out.println("sum = " + sum); // 153
可以把两层循环的职责分开理解:
- 外层循环负责“现在要算到几的阶乘”,并负责累加;
- 内层循环负责“把当前这个数的阶乘算出来”。
运行结果是 153,因为:
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
1 + 2 + 6 + 24 + 120 = 153
不过这个写法每次都从头计算阶乘,效率不高。可以复用上一次的结果:
int factorial = 1;
int sum = 0;
int i = 1;
while (i <= 5) {
factorial *= i; // 利用上一次的 (i - 1)! 直接得到 i!
sum += factorial;
i++;
}
System.out.println(sum); // 153
这样每个阶乘只算一次,思路也更简洁。后面学了 for 之后,这段代码还可以写得更紧凑。
for:把循环三要素写在一起
for 循环把“初始化、条件、更新”集中写在括号里,适合循环次数比较明确的场景:
for (表达式1; 布尔表达式2; 表达式3) {
// 循环体
}
执行顺序是:
- 先执行一次
表达式1; - 判断
表达式2,为true就执行循环体; - 循环体执行完,执行
表达式3; - 回到第 2 步继续判断。
例子:用 for 计算 5 的阶乘
int result = 1;
for (int i = 1; i <= 5; i++) {
result *= i;
}
System.out.println("result = " + result); // 120
i 只在 for 循环内部有效,循环结束后就不能再访问了。这是一个很好的作用域习惯:循环变量只在需要它的地方出现。
例子:用嵌套 for 求阶乘和
int sum = 0;
for (int i = 1; i <= 5; i++) {
int tmp = 1;
for (int j = 1; j <= i; j++) {
tmp *= j;
}
sum += tmp;
}
System.out.println("sum = " + sum); // 153
这段代码和前面的 while 版本思路一致,只是把循环变量集中到了 for 的括号里,读起来更紧凑。
while 和 for 怎么选?一个简单的判断方法是:
- 次数明确,比如“从 1 循环到 5”,优先用
for; - 次数不明确,比如“一直猜,直到猜中为止”,优先用
while; - 两者可以互相改写,选可读性更好的那个。
break 与 continue:提前结束和跳过
break 和 continue 都用于控制循环,但作用不同:
| 关键字 | 作用 |
|---|---|
break | 立即结束最近的一层循环或 switch |
continue | 跳过本次循环剩余的语句,直接进入下一次循环 |
return | 结束当前方法,后面的代码都不再执行 |
例子:找 100 到 200 中第一个 3 的倍数
int num = 100;
while (num <= 200) {
if (num % 3 == 0) {
System.out.println("找到了 3 的倍数, 为:" + num);
break;
}
num++;
}
输出结果是:
找到了 3 的倍数, 为:102
找到第一个满足条件的数之后,break 直接结束循环,不再继续检查后面的数。
这里有一个容易踩的坑:练习代码的注释里提到“把 break 换成 continue 就能找到 100 到 200 中所有 3 的倍数”,但在这段 while 里直接替换会出问题——continue 会跳过后面的 num++,导致 num 一直停在 102,变成死循环。
更稳妥的写法是改用 for:
for (int num = 100; num <= 200; num++) {
if (num % 3 != 0) {
continue;
}
System.out.println(num);
}
for 的 num++ 会在每次循环结束后执行,即使这一轮执行了 continue,循环变量仍然会更新,所以不会死循环。
如果一定要在 while 里使用 continue,要确保更新语句在 continue 之前执行:
while (num <= 200) {
if (num % 3 == 0) {
System.out.println(num);
num++;
continue;
}
num++;
}
这个细节说明:continue 本身不难,难的是它跳过了哪些语句。
输入输出:让程序能和人交互
三种常见输出方式
int msg = 0;
System.out.println(msg); // 输出并换行
System.out.print(msg); // 输出但不换行
System.out.printf("%d\n", 19); // 格式化输出
printf 常用的格式占位符:
| 占位符 | 含义 |
|---|---|
%d | 整数 |
%s | 字符串 |
%f | 浮点数 |
%.2f | 保留两位小数 |
%n | 平台相关的换行符 |
例如:
System.out.printf("年龄:%d,成绩:%.2f%n", 18, 95.5);
用 Scanner 读取键盘输入
import java.util.Scanner;
Scanner sc = new Scanner(System.in);
System.out.println("读入一个字符串:");
String str = sc.nextLine();
System.out.println(str);
System.out.println("读入一个整型:");
int age = sc.nextInt();
System.out.println(age);
// nextInt() 只读走整数,不读走后面的换行符
// 这里先消费掉换行,避免影响后面的输入
sc.nextLine();
System.out.println("读入一个小数:");
float f = sc.nextFloat();
System.out.println(f);
Scanner 常见方法:
nextLine():读取一整行,包括空格;next():读取一个“单词”,遇到空白就停止;nextInt()、nextFloat()、nextDouble():读取对应类型的数据。
这里最容易遇到的问题是:nextInt() 读完后,换行符还留在输入缓冲区里。如果紧接着调用 nextLine(),可能直接读到一个空字符串。练习代码里的 sc.nextLine() 就是在消费这个换行符。
另外,如果输入的类型不匹配,比如在 nextInt() 时输入了字母,会抛出 InputMismatchException。实际项目中可以先用 hasNextInt() 判断,再做读取。
小项目:猜数字
把随机数、循环、分支和输入组合起来,就是一个很经典的猜数字游戏:
import java.util.Random;
import java.util.Scanner;
Random random = new Random();
int randnum = random.nextInt(100); // 生成 [0, 100) 的随机整数
System.out.println("随机数是:" + randnum);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入你要猜的数字:");
int num = sc.nextInt();
if (num < randnum) {
System.out.println("小了");
} else if (num == randnum) {
System.out.println("正确");
break;
} else {
System.out.println("大了");
}
}
几个关键点:
Random.nextInt(100)的范围是 左闭右开 的[0, 100),不包含 100;while (true)表示“一直猜”,真正的出口是猜中后的break;- 先判断“小了”,再判断“相等”,最后用
else处理“大了”,保证三种情况互斥; - 每次循环都会重新读取一个数,所以不会卡住;
- 练习时我把随机数打印出来方便调试,真正玩的时候把这行删掉即可。
如果想把它改得更完整,可以继续做这些改进:
- 把范围改成 1 到 100:
random.nextInt(100) + 1; - 增加一个计数器,记录猜了几次;
- 用
hasNextInt()处理用户输入非数字的情况; - 限制最大猜测次数,超过次数就结束游戏。
几个容易踩的坑
- 把
==写成=。if (a = 1)在 Java 中会编译报错,因为赋值表达式的类型是int,不是boolean;比较是否相等要用==。 switch忘记break。 代码会继续执行下一个case,常常导致多打印内容。- 循环变量不更新。
while里忘记i++或n++,很容易死循环。 continue跳过了更新语句。 尤其在while中,先确认continue后面的i++还能不能执行到。nextInt()后直接nextLine()。 换行符可能被下一次读取当成空行,需要额外消费一次。- 误以为
nextInt(100)包含 100。 它的上界是开区间,想要 1 到 100 要写成nextInt(100) + 1。 switch使用了不支持的类型。long、float、double、boolean都不能作为选择表达式的类型。
小结
把这篇的内容压缩成一张表:
| 语句 | 适合场景 | 关键点 |
|---|---|---|
if / else if / else | 范围判断、复杂条件 | 条件是 boolean,一组分支最多命中一个 |
switch | 固定值的等值判断 | case 是常量,注意 break 和类型限制 |
while | 次数不固定的循环 | 先判断后执行,记得更新循环变量 |
for | 次数明确的循环 | 初始化、条件、更新写在一起 |
break | 提前结束循环或 switch | 只跳出最近一层 |
continue | 跳过本次循环剩余部分 | 注意不要跳过循环变量的更新 |
逻辑控制看起来语句不多,但真正写起来,难点往往在边界和退出条件。建议先把文中的闰年、阶乘、找倍数和猜数字自己敲一遍,再尝试改需求,比如“输出所有偶数”“统计猜数字次数”“求 1 到 100 的和”,这样才能把语法变成自己的东西。
本文系统讲解JavaSE中核心逻辑控制结构,包括if分支判断(含闰年判定)、switch等值匹配(支持JDK14+箭头语法)、while与for循环(含阶乘及嵌套计算)、break/continue流程控制,以及Scanner键盘输入与三种输出方式。重点剖析边界条件、循环变量更新、死循环规避、输入缓冲区处理等常见陷阱,结合猜数字小项目整合实践。
333

被折叠的 条评论
为什么被折叠?



