第二单元 · 分支、循环、数组 · 第 4 / 12 节
if 与 switch:让程序选一条路
写 if、if-else、else-if 三种分支,用 switch 对 int 做多路分支,正确使用 break/default,分清区间判断与等值判断。
这一节仍然按“左边网页、右边终端”的方式做:网页给你完整源码和命令,终端里真的编译、运行、看输出。四种东西不要混:$ 开头的是 shell 命令,java 代码块里是完整源文件,output 是程序的标准输出(stdout),input 是提前准备好的标准输入(stdin,只读)。
第一单元你已经会写 Hello.java、用变量保存数据、用 Scanner 读整数,也会用 >、>=、== 得到 boolean。这一节只加“选择”:条件成立走哪条路,不成立走哪条路。本节不用循环、数组和自定义方法;main 仍然是入口外壳,先照写,不展开讲方法。
本节所有命令都在同一个 SSH 窗口里执行。新开窗口时,先运行 source ~/java-practice/env.sh,再确认 java -version 和 javac -version 都是 17.x;不是 17.x 就回 setup.html,不要继续。source 可能打印一行环境提示,那是 shell 的输出,不要和程序 output 混。
$ 提示符;输出和输入示例没有复制按钮。已完成 0 / 7 步
第 1 步:条件成立才执行一个代码块
if 的骨架是:
if (条件) {
条件为 true 时执行的语句;
}
条件必须是 boolean,比如 score >= 60。即使花括号里只有一行,也建议写花括号:以后往里加第二行时,不会因为少花括号而改变执行范围。
先预测:score 是 86,下面的程序会打印几行?如果把 86 改成 52,又会打印什么?
先建目录,写好 IfDemo.java。如果同名文件已经存在,if [ -e ... ] 那行会先把它改名备份,不会覆盖你上次写的内容;$(date +%s) 是当前时间戳,保证备份名不重复。
$ source ~/java-practice/env.sh
$ java -version
$ javac -version
$ mkdir -p ~/java-practice/lesson04
$ cd ~/java-practice/lesson04
$ if [ -e IfDemo.java ]; then mv IfDemo.java "IfDemo.java.bak.$(date +%s)"; fi
$ nano IfDemo.java
nano 打开后,把下面这个完整文件粘进去。按 Ctrl+O,回车确认文件名,再按 Ctrl+X 退出。保存后回到 shell,再编译运行:
public class IfDemo {
public static void main(String[] args) {
int score = 86;
if (score >= 60) {
System.out.println("及格");
}
System.out.println("检查结束");
}
}
$ ls -l IfDemo.java
$ javac -encoding UTF-8 IfDemo.java
$ java IfDemo
及格
检查结束
86 >= 60 是 true,所以进入花括号打印“及格”。花括号结束后,System.out.println("检查结束"); 不在 if 里面,它总会执行。把 86 改成 52 再编译运行,52 >= 60 是 false,花括号被跳过,只剩“检查结束”。
提示 先看条件,再看花括号范围
读一段分支代码时,先找到 if (条件),算出它是 true 还是 false;再只看对应的花括号。不要从第一行开始逐行猜。
参考答案 先预测:改成 52 会怎样
只会输出:
检查结束
因为 52 >= 60 为 false,if 的花括号没有执行。注意“检查结束”仍然会打印,它在 if 外面。
第 2 步:if-else 处理两条互斥的路
两条路只能走一条时,用 if-else:
if (条件) {
条件为 true 时执行;
} else {
条件为 false 时执行;
}
else 后面不写条件,它表示“上面那个条件不成立时”。else 不能单独出现,必须紧跟在某个 if 后面。
先预测:score 是 52,程序会走哪边?如果改成 86 呢?
新建 IfElseDemo.java(还是先备份同名旧文件):
$ cd ~/java-practice/lesson04
$ if [ -e IfElseDemo.java ]; then mv IfElseDemo.java "IfElseDemo.java.bak.$(date +%s)"; fi
$ nano IfElseDemo.java
public class IfElseDemo {
public static void main(String[] args) {
int score = 52;
if (score >= 60) {
System.out.println("及格");
} else {
System.out.println("不及格");
}
System.out.println("检查结束");
}
}
$ javac -encoding UTF-8 IfElseDemo.java
$ java IfElseDemo
不及格
检查结束
52 >= 60 是 false,所以跳过 if 的花括号,进入 else,打印“不及格”。两条分支是互斥的:不可能既打印“及格”又打印“不及格”。把 52 改成 86,就会走 if 那边,只打印“及格”。
| score | score >= 60 | 走哪边 |
|---|---|---|
| 52 | false | else:不及格 |
| 60 | true | if:及格 |
| 86 | true | if:及格 |
注意 60 这个边界:>= 包含等于,所以 60 算及格;如果写成 >,60 就会掉到“不及格”。边界用哪个运算符,要先想清楚业务含义。
第 3 步:else-if 做多级区间判断
分数不止“及格/不及格”两档,而是优秀、良好、及格、不及格四档。这时用 else-if 把多个条件串起来:
if (条件1) {
条件1为 true;
} else if (条件2) {
条件2为 true;
} else if (条件3) {
条件3为 true;
} else {
前面都不成立;
}
执行规则是**从上往下,遇到第一个为 true 的条件就进入它的花括号,后面的 else-if 和 else 全部跳过**。所以顺序很重要:用 >= 判断时从高到低排,用 < 判断时从低到高排;核心是不能把宽条件放在窄条件前面。
先预测:score = 86 会打印哪一档?如果把 score >= 60 放到最前面,86 又会打印什么?
$ cd ~/java-practice/lesson04
$ if [ -e ElseIfDemo.java ]; then mv ElseIfDemo.java "ElseIfDemo.java.bak.$(date +%s)"; fi
$ nano ElseIfDemo.java
public class ElseIfDemo {
public static void main(String[] args) {
int score = 86;
if (score >= 90) {
System.out.println("优秀");
} else if (score >= 80) {
System.out.println("良好");
} else if (score >= 60) {
System.out.println("及格");
} else {
System.out.println("不及格");
}
}
}
$ javac -encoding UTF-8 ElseIfDemo.java
$ java ElseIfDemo
良好
86 >= 90 是 false,所以看下一个;86 >= 80 是 true,进入“良好”,后面的 86 >= 60 和 else 都不再判断。else-if 不是每个条件都算一遍,而是遇到第一个 true 就停。
如果把 score >= 60 挪到最前面,86 会先满足 86 >= 60,直接打印“及格”,后面的“良好”永远轮不到。这不是编译器的问题,是顺序写错了。
注意 区间判断不要漏掉边界
score >= 80 这一档实际上表示“80 到 89”,因为 90 及以上已经被前一个 if 截走了。写多级判断时,先画一张区间表,再写条件,别一边写一边猜。
第 4 步:用 Scanner 把分数交给分支
前几步的分数写死在代码里。现在把第一单元学过的 Scanner 接回来:程序先打印提示,再等用户输入一个整数,然后走同一套 else-if。
Scanner 不在 java.lang 里,所以文件第一行要写 import java.util.Scanner;。import 只是告诉编译器“去 java.util 里找这个类”,现在照写即可;本课程后面不会再要求你背更多包名。
新建 GradeInputDemo.java:
$ cd ~/java-practice/lesson04
$ if [ -e GradeInputDemo.java ]; then mv GradeInputDemo.java "GradeInputDemo.java.bak.$(date +%s)"; fi
$ nano GradeInputDemo.java
import java.util.Scanner;
public class GradeInputDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入分数:");
int score = sc.nextInt();
if (score >= 90) {
System.out.println("优秀");
} else if (score >= 80) {
System.out.println("良好");
} else if (score >= 60) {
System.out.println("及格");
} else {
System.out.println("不及格");
}
}
}
先编译。然后用 printf 把一行输入送进程序;printf 是 shell 命令,不是 Java 语法,\n 表示换行。这样每次运行都有固定输入,方便对照。
$ javac -encoding UTF-8 GradeInputDemo.java
$ printf '86\n' | java GradeInputDemo
交互输入(示例,只读):
86
请输入分数:
良好
再换一个不及格的输入:
$ printf '42\n' | java GradeInputDemo
交互输入(示例,只读):
42
请输入分数:
不及格
提示语“请输入分数:”也是程序的标准输出,所以它会出现在 output 块里;86 只出现在 input 块里,不会被打印。程序真正读到的就是 86 这个整数,Scanner.nextInt() 会跳过空白并解析一个 int。
先预测:输入 60 和 59 分别会打印什么?再自己运行对照。
参考答案 先预测:60 与 59
60 满足 60 >= 60,打印“及格”;59 不满足任何 >= 60 的条件,落到 else,打印“不及格”。边界值最值得测,因为它们最容易暴露 > 和 >= 写错。
第 5 步:switch 对 int 做等值多路分支
switch 适合“一个整数变量,对几个固定值分别做不同的事”。它做的是等值比较,不是区间比较:
switch (整数表达式) {
case 1:
语句;
break;
case 2:
语句;
break;
default:
没有匹配时执行;
break;
}
case后面必须是常量,不能写case score > 60这种条件。break的作用是立刻跳出整个 switch;没有break会继续往下执行,叫“穿透”(fallthrough)。default在所有case都不匹配时执行;它不一定要放最后,但初学者放最后最清楚。- 本节只对
int用switch;字符串和枚举不在本阶段。
先预测:menu = 2 会打印哪一行?menu = 9 呢?
$ cd ~/java-practice/lesson04
$ if [ -e SwitchMenuDemo.java ]; then mv SwitchMenuDemo.java "SwitchMenuDemo.java.bak.$(date +%s)"; fi
$ nano SwitchMenuDemo.java
public class SwitchMenuDemo {
public static void main(String[] args) {
int menu = 2;
switch (menu) {
case 1:
System.out.println("查看成绩");
break;
case 2:
System.out.println("修改成绩");
break;
case 3:
System.out.println("退出");
break;
default:
System.out.println("无效选项");
break;
}
System.out.println("菜单处理结束");
}
}
$ javac -encoding UTF-8 SwitchMenuDemo.java
$ java SwitchMenuDemo
修改成绩
菜单处理结束
menu 是 2,程序直接跳到 case 2,打印“修改成绩”,遇到 break 跳出 switch,再执行 switch 后面的“菜单处理结束”。把 menu 改成 9,所有 case 都不匹配,走 default,打印“无效选项”。
switch 不是“更高级的 if”。它只能做等值匹配;要判断“大于 80 且小于 90”这种区间,仍然用 else-if。第 7 步会把两者放在一起对比。
第 6 步:故意漏掉 break,观察穿透
break 漏写时,编译器不会报错,程序照常运行,只是会从匹配的 case 一直往下执行,直到遇到 break 或 switch 结束。这种错误比编译错误更难发现,因为输出“看起来像程序在正常工作”。
先预测:menu = 1,下面的 case 1 末尾没有 break,会打印几行?
$ cd ~/java-practice/lesson04
$ if [ -e SwitchFallthroughDemo.java ]; then mv SwitchFallthroughDemo.java "SwitchFallthroughDemo.java.bak.$(date +%s)"; fi
$ nano SwitchFallthroughDemo.java
public class SwitchFallthroughDemo {
public static void main(String[] args) {
int menu = 1;
switch (menu) {
case 1:
System.out.println("查看成绩");
case 2:
System.out.println("修改成绩");
break;
case 3:
System.out.println("退出");
break;
default:
System.out.println("无效选项");
break;
}
}
}
$ javac -encoding UTF-8 SwitchFallthroughDemo.java
$ java SwitchFallthroughDemo
查看成绩
修改成绩
menu 匹配 case 1,打印“查看成绩”;因为没有 break,程序继续执行 case 2 的语句,打印“修改成绩”,直到遇到 case 2 里的 break 才停。穿透不是“重新判断 menu 是不是 2”,它只是顺着代码往下走。
修法很简单:在 case 1 的打印语句后面补一个 break;,就回到第 5 步那种“只执行一个分支”的行为。也有故意利用穿透的写法,比如多个 case 共用一个处理块,但初学阶段先养成“每个 case 都写 break”的习惯。
注意 漏 break 不会报错
如果 switch 的输出多了一行,先数每个 case 后面有没有 break;。不要只看最后一个 case;穿透会从匹配点一路往下串。
第 7 步:区间用 else-if,等值用 switch
现在把两种分支放在同一个程序里,练习“选工具”:
- 年龄分三档(未成年、成人、老年)是区间判断,用
if / else-if / else。 - 座位号 1、2、其他是固定值匹配,用
switch。
先预测:age = 17、seat = 2,程序会打印哪两行?
$ cd ~/java-practice/lesson04
$ if [ -e BranchPickDemo.java ]; then mv BranchPickDemo.java "BranchPickDemo.java.bak.$(date +%s)"; fi
$ nano BranchPickDemo.java
public class BranchPickDemo {
public static void main(String[] args) {
int age = 17;
if (age < 18) {
System.out.println("未成年票");
} else if (age < 60) {
System.out.println("成人票");
} else {
System.out.println("老年票");
}
int seat = 2;
switch (seat) {
case 1:
System.out.println("靠窗");
break;
case 2:
System.out.println("靠过道");
break;
default:
System.out.println("中间");
break;
}
}
}
$ javac -encoding UTF-8 BranchPickDemo.java
$ java BranchPickDemo
未成年票
靠过道
年龄是连续区间,所以第一个 age < 18 成立就打印“未成年票”,后面的 else-if 不再判断。座位号是离散的固定值,switch 直接跳到 case 2,打印“靠过道”,break 跳出。
如果座位 1 和 2 都要算“靠窗”,可以写两个叠在一起的 case,共用一个处理块和 break。这属于“故意穿透”的写法,等熟练后再用;现在先保证每个 case 都有 break。
说明 switch 的 case 不能写区间
case age < 18: 不是合法的 case 标签。case 后面只能是常量。区间判断请回到 if / else-if,不要硬塞进 switch。
常见错误
- 现象:
javac报incompatible types: int cannot be converted to boolean,指向if (score = 60)。原因:=是赋值,if需要的是boolean表达式。先检查: 把=改成==(等值)或>=(区间),再确认条件两边类型能比较。 - 现象:
if后面明明写了条件,下面的语句却总是执行,或者else报'else' without 'if'。原因: 在if (...)后面多写了一个分号,变成空语句;或者else前面没有紧挨着完整的if。先检查: 看if (...)右括号后有没有;,以及else是否和它的if在同一层花括号里。 - 现象:
switch打印了不止一个 case 的内容。原因: 某个case末尾漏了break;,发生穿透。先检查: 从匹配的 case 开始,逐个看打印语句后面有没有break;;不要只看最后一个 case。 - 现象:
javac报duplicate case label或case label must be a constant expression。原因: 两个case写了同一个值,或case后面写了变量/条件。先检查: 列出所有 case 值,确认不重复;case 只能是字面量常量。 - 现象: 运行
GradeInputDemo时抛出InputMismatchException,或者程序一直停着不动。原因: 输入的不是整数(比如带了小数点、字母),或交互运行时还没按回车。先检查: 对照input块,确认第一行就是一个纯整数;用printf固定输入时检查\n是否写对。 - 现象:
javac报illegal character ':'或';'。原因: 代码里混入了中文全角冒号、分号或括号。先检查: 看报错行号,把:改成:、;改成;、()改成();写代码时保持输入法在英文状态。
练习
先自己写、先预测,再展开答案。练习题也用 JDK 17 实际编译运行过,答案里的输出就是程序真实的标准输出。
练习 1:五级成绩
把第 3 步的四档改成五档:>= 90 优秀、>= 80 良好、>= 70 中等、>= 60 及格,其余不及格。用 score = 75 运行,应该输出“中等”。要求仍然只用 if / else-if / else,不用循环和数组。
参考答案 练习 1:五级成绩
完整文件存成 Grade5Demo.java:
public class Grade5Demo {
public static void main(String[] args) {
int score = 75;
if (score >= 90) {
System.out.println("优秀");
} else if (score >= 80) {
System.out.println("良好");
} else if (score >= 70) {
System.out.println("中等");
} else if (score >= 60) {
System.out.println("及格");
} else {
System.out.println("不及格");
}
}
}
编译运行:
$ javac -encoding UTF-8 Grade5Demo.java
$ java Grade5Demo
中等
75 不满足前三个条件,满足 75 >= 70,所以进入“中等”;后面的 >= 60 不再判断。
练习 2:给菜单加一项
在 SwitchMenuDemo.java 的基础上加 case 4,打印“打印帮助”,保留 default。用 menu = 4 运行,应该输出“打印帮助”。写完先自己检查每个 case 是否有 break。
参考答案 练习 2:给菜单加一项
完整文件存成 MenuHelpDemo.java:
public class MenuHelpDemo {
public static void main(String[] args) {
int menu = 4;
switch (menu) {
case 1:
System.out.println("查看成绩");
break;
case 2:
System.out.println("修改成绩");
break;
case 3:
System.out.println("退出");
break;
case 4:
System.out.println("打印帮助");
break;
default:
System.out.println("无效选项");
break;
}
}
}
编译运行:
$ javac -encoding UTF-8 MenuHelpDemo.java
$ java MenuHelpDemo
打印帮助
menu 是 4,直接匹配 case 4;break 让程序跳出 switch,不会继续走 default。
练习 3:先预测,不运行
下面的代码只是片段,不用单独存成文件;先口算 score = 90 时会打印什么,再展开答案。
int score = 90;
if (score > 60) {
System.out.println("第一档");
} else if (score > 80) {
System.out.println("第二档");
} else {
System.out.println("第三档");
}
参考答案 练习 3:顺序陷阱
打印“第一档”。90 > 60 已经成立,程序进入第一个分支后就不再判断后面的 else if。虽然 90 > 80 也成立,但它永远轮不到。这个例子说明:else-if 的顺序会改变结果,范围判断要从高到低或从低到高排好,不能把宽条件放在窄条件前面。
做到这里
做到这里今天可以停。你已经会:
- 用
if让一段代码只在条件成立时执行; - 用
if-else处理两条互斥的路; - 用
else-if做多级区间判断,并知道顺序会影响结果; - 用
switch对int做等值分支,知道break和default的作用; - 用固定
input配合Scanner测试不同分数。
下一节才学 for、while 和循环里的 break、continue,以及浅层嵌套。本节没有数组、没有自定义方法;不要在练习里提前用 for 遍历或写 static 辅助方法。
来源 来源与改编说明(非官方)
主题参考:黑马程序员 Java 入门到起飞(BV17F411T7Ao,2022-04-27)P039–P044 的顺序结构、if 三种格式、switch 及扩展主题。本站为独立原创讲解,非官方站点/教材,未获授权,不逐字复刻视频或讲义;示例、错误清单与练习为原创补充。详见 source/SOURCE.md。
说明:本站是个人学习整理,不是官方教材;不包含原课程的书本、视频或受版权保护的整本内容。原课程与商标归其权利人。