适用读者:从别的语言转 Java,或想补齐"细节坑"的人。
前置:S04-B02。
实测环境:OpenJDK 21.0.12.1(2026-11-05)。
覆盖旧文:A-1703(84) A-1701(209) = 293 点。
上一篇:S04-B02 · 基本类型与装箱:==什么时候会骗你
下一篇:S04-B04 · 数组、static 与初始化顺序。
1. 一句话结论
Java 的控制流本身不难,难的是几个"看起来一样、行为不同"的细节:i++ 与 ++i、&& 与 &、switch 的穿透、以及整数除法/取模的符号。
实测一次跑完:
i = i++ 结果: 0
j = ++j 结果: 1
短路求值(false && ...): 结果 false ← 右边没执行
非短路(false & ...): B2 结果 false ← 右边执行了
switch(x=2) 无 break: two three
5/2 = 2 , 5%2 = 1 , -5/2 = -2 , -5%2 = -1
1<<3 = 8 , -8>>1 = -4 , -8>>>1 = 2147483644
2. i++ 与 ++i:为什么 i = i++ 还是 0
| 表达式 | 求值顺序 | 结果 |
|---|---|---|
i++ |
先取值,再加 | 赋回去的是旧值 |
++i |
先加,再取值 | 赋回去的是新值 |
i = i++ 展开:
① 读 i(0)
② i 自增(i 变 1)
③ 把①读到的旧值赋给 i(i 变回 0)
实测 0。这条是"面试题",也是"真实 bug"——别在一行里对同一变量既读又写。
3. && 与 &:短路与非短路
| 运算符 | 短路 | 适用 |
|---|---|---|
&& / || |
✅ | 逻辑判断(默认用这个) |
& / | |
❌ | 位运算(整数)或"必须都执行"的场景 |
实测:false && side("B") 右边没执行(无 B 输出);false & side("B2") 右边执行了。
实践:if (obj != null && obj.isValid()) 必须用 &&,否则 & 会 NPE。
4. switch:默认穿透
实测:x=2 且没有 break → 输出 two three(从命中点一路往下)。
| 类型 | 支持 |
|---|---|
byte/short/char/int |
✅ 传统 switch |
String(Java 7+) |
✅ |
enum |
✅ |
switch 表达式(Java 14+) |
✅ 箭头语法,不穿透 |
// 现代写法(Java 14+),没有穿透问题
String s = switch (x) {
case 1 -> "one";
case 2 -> "two";
default -> "other";
};
判据:新代码用箭头 switch,老代码检查 break。
5. 除法与取模:符号跟着被除数
| 表达式 | 结果 | 说明 |
|---|---|---|
5 / 2 |
2 | 整数除法截断 |
5 % 2 |
1 | — |
-5 / 2 |
-2 | 向零取整 |
-5 % 2 |
-1 | 符号与被除数一致 |
坑:a % b == 0 判断整除在负数下仍成立,但 a % b 做"环形索引"时要处理负值:((i % n) + n) % n。
6. 位运算:>> 与 >>>
| 运算 | 含义 | 实测(-8) |
|---|---|---|
>> |
算术右移(保留符号位) | -4 |
>>> |
逻辑右移(补 0) | 2147483644 |
判据:处理无符号语义(如哈希、CRC)必须 >>>;对负数用 >> 会"越移越小"。
7. 五个误区
| 误区 | 真相 |
|---|---|
"i = i++ 等于自增" |
结果不变(先取值后赋值) |
"& 和 && 一样" |
& 不短路,可能 NPE / 多执行 |
| "switch 默认 break" | 默认穿透 |
"-5 % 2 == 1" |
是 -1 |
">> 和 >>> 一样" |
负数时不同 |
8. 自检
i = i++后i是多少?为什么?false & side()与false && side()的行为差异?switch穿透如何避免?现代写法是什么?-5 % 2等于几?做环形索引怎么写?- 处理哈希值时该用
>>还是>>>?
9. 下一篇
S04-B04 · 数组、static 与初始化顺序
控制流之后是"结构与初始化":数组为什么是对象、static 到底什么时候执行,以及六步初始化顺序的实测。
本篇属于系列《S04 · Java 语言核心》。旧文来源:A-1703/A-1701。所有输出为 OpenJDK 21 实测。