前两篇我们建立了一个印象:程序从 main 的第一行开始,逐句往下执行到结尾,像一条流水线。上一篇里代码打印 age、打印 height——变量的值可以换,但程序干的活永远一样:它没法回答“成绩到 60 就打印‘及格’,不到就打印‘不及格’”这种需求。
真实程序恰恰靠这个能力活着:余额不足就拒绝支付,闹钟到点才响铃。Java 把这种能力叫做 条件分支,最基础的写法是 if / else。本篇只解决一个问题:程序如何根据一个“真还是假”的条件,选择执行哪一段语句。循环(让一段代码反复执行)留到下一篇。
先看一个完整例子:
1int score = 85;
2
3if (score >= 60) {
4 System.out.println("及格");
5} else {
6 System.out.println("不及格");
7}运行结果只打印“及格”。读法是:如果 score >= 60 这个判断为真,就执行第一对花括号里的语句;否则执行 else 后那一对花括号里的语句。
if 的骨架:括号里必须是一个 boolean
if 的句式固定为 if (条件) { 语句 },括号里的条件必须是一个 boolean。上一篇我们认识了 boolean 类型——变量只能装 true 或 false。现在它派上了真正的用场:if 靠这个值决定走哪条路。
布尔值最常见的来源不是变量,而是 比较运算符。比较就像一次提问,答案只有“是”或“否”:
| 运算符 | 含义 | 为真的例子 |
|---|---|---|
> | 大于 | 5 > 3 |
>= | 大于等于 | 5 >= 5 |
< | 小于 | 3 < 5 |
<= | 小于等于 | 3 <= 3 |
== | 相等(注意是两个等号) | score == 90 |
!= | 不相等 | score != 90 |
所以 score >= 60 本身就是个 boolean 值——它不是在描述一个已经存在的事实,而是当场算出一个 true 或 false。
更复杂的条件可以组合出来:&& 读作“且”(两边都为真才为真),|| 读作“或”(只要一边为真就为真),! 读作“非”(把 true 变 false、false 变 true)。官方教程特别指出,&& 和 || 有“短路”行为:右边只在必要时才会被求值 2。例如:
1if (score >= 90 && score <= 100) { // 只有 90 到 100 之间才为真
2 System.out.println("成绩在优秀区间");
3}这里能再次看到强类型的把关:if 括号里只接受 boolean。C 语言允许 if (1)(把 1 当“真”用),Java 里这样写直接编译报错——javac 在运行前就用上一篇说的那张“类型规格单”拦下了你。
if 也可以不带 else 单独使用:条件为假时什么都不做,程序直接跳到 if 语句之后继续往下走。
else:为“条件不成立”准备第二条路
if 后面跟 else,读作“否则”。走 if 分支还是 else 分支,取决于括号里算出的值,两条路互相排斥,同一时刻只会执行其中一支。上一节那个打印“及格/不及格”的例子,就是最标准的二选一。
多条路:else if 自上而下、命中即停
真实判断常常不止两个分支。比如分数分四档:
1int score = 85;
2
3if (score >= 90) {
4 System.out.println("优秀");
5} else if (score >= 80) {
6 System.out.println("良好");
7} else if (score >= 60) {
8 System.out.println("及格");
9} else {
10 System.out.println("不及格");
11}输出“良好”。这条链有两个关键规则。第一,自上而下 逐条检查;第二,命中即停——85 >= 80 为真,打印完“良好”后整条链立即结束,后面的条件不再看,哪怕 85 >= 60 也为真。Java 官方教程用同一个等第例子专门点明:一旦某个条件被满足,就执行相应语句,剩余条件不再被求值 1。
“命中即停”有个直接后果:条件的顺序本身就是程序逻辑的一部分。如果把 score >= 60 挪到最前面,那么 90 分也会先命中它、打印“及格”后停住,“优秀”永远轮不到。写 else if 链时,越苛刻的条件要放越前面。
还有一点:else if 并不是什么新语法,它就是“else 后面再跟一个 if”,读作“否则,再看下一个条件”。拆开看就明白为什么链条能无限接下去。
花括号:决定哪些语句属于这个分支
一个分支里当然可以放多条语句。if 后面跟的 语句块(block)就是一对花括号把若干语句圈成一个整体 3。花括号划定了每个分支的地盘。
如果偷懒不写花括号,分支只管辖紧随其后的 第一条 语句:
1if (score >= 60)
2 System.out.println("及格");
3 System.out.println("欢迎再来"); // 无条件执行!缩进让“欢迎再来”看起来像分支的一部分,但 Java 不看缩进——它只看结构。没有花括号,if 只绑定了第一条打印语句,第二行不管分数多少都会执行。Java 官方教程对 block 的定义值得记住:一对花括号之间的语句组成了一个整体,凡是允许写一条语句的地方都可以放一个 block 3。
省略花括号还埋伏着更隐蔽的事故,叫 悬挂 else(dangling else)。配对规则是:else 总是配对它前面最近的、还没配对的 if,缩进不参与配对。看这段代码:
1int score = 92;
2
3if (score >= 90)
4 if (score >= 95)
5 System.out.println("A+");
6else
7 System.out.println("低于 90");缩进暗示 else 属于外层 if (score >= 90)——意图是分数不到 90 时提示“低于 90”。但按 Java 规则,else 配的是内层 if (score >= 95):92 分时会打印出荒谬的“低于 90”。改成 80 分,外层条件为假,整段直接跳过,本应出现的提示也没了。
两个事故的修法完全相同——每层都写花括号:
1if (score >= 90) {
2 if (score >= 95) {
3 System.out.println("A+");
4 } else {
5 System.out.println("90 到 94 之间");
6 }
7} else {
8 System.out.println("低于 90");
9}把“每层 if 都带花括号”练成习惯,哪怕分支只有一行。这不是风格偏好,是规避一类真实 bug 的最低成本手段。
判断相等写 ==,赋值才写 =
最后回到那个最容易踩的坑:== 和 =。上一篇我们学过,= 是“装入”——先算右边,再把结果写进左边变量;而 if 需要的“问一问两边是否相等”是 == 的职责。弄混了会怎样?
1int score = 85;
2if (score = 90) { // 编译报错:int 不是 boolean
3 ...
4}这行 Java 编译不过:javac 检查类型时发现括号里是个 int,不是 boolean。强类型在这里救了你一次——换成 C 语言,这行会顺利通过,还会顺便把 score 改成 90(连你的数据都污染了)。
但有个更阴险的变体。如果左边恰好是 boolean 变量:
1boolean isStudent = false;
2if (isStudent = true) { // 能编译!但这不是判断,是赋值
3 ...
4}if (isStudent = true) 能通过编译:它做的不是“问 isStudent 是否为 true”,而是“把 true 装进 isStudent”,于是条件恒为真,这个分支永远被走进。你盯着代码看,以为自己在做判断,其实在做赋值——这是最难排查的一类静默 bug。记忆法:= 改变变量的值,== 不改变任何东西、只产出一个 true 或 false。判断用 ==,赋值用 =。
到这里,条件分支的核心闭环已经完成:if 是“括号里的 boolean 为真才执行花括号内的语句”;else 兜底、else if 提供自上而下、命中即停的多路判断;花括号划定分支地盘,== 负责提问,= 负责装值。程序从此会“看情况办事”了,但还有一种更常见的需求它还不满足——让同一段代码反复执行,直到某个条件改变。下一篇就轮到 while 和 for。