if-else 选择结构
"代码是给机器执行的,但首先是给人读的。" —— 大翔在飞翔科技代码评审会上对实习生小崔说,"if-else 看似简单,但嵌套层级、条件顺序、边界处理,每一个细节都体现着程序员的工程素养。"
概述
程序不可能永远按照一条直线执行——它需要像十字路口一样,根据不同条件选择不同的执行路径。if-else 是 Java 中最基础、最灵活的选择结构,它能根据布尔表达式的结果决定代码走向。
三种基本形式
| 形式 | 语法 | 适用场景 |
|---|---|---|
| 单分支 if | if (条件) { 语句; } | 只有一个条件满足时执行某操作 |
| 双分支 if-else | if (条件) { 语句A; } else { 语句B; } | 二选一的分支逻辑 |
| 多分支 if-else if-else | if (条件1) { ... } else if (条件2) { ... } else { ... } | 多条件互斥判断 |
一、单分支 if
语法格式
if (布尔表达式) {
// 条件为 true 时执行的代码块
}
执行流程:先计算布尔表达式的值,若为
true则执行花括号内的代码块;若为false则跳过。
关键点
| 关键点 | 说明 |
|---|---|
| 条件必须返回 boolean | Java 中 if 的条件只能是 boolean 类型,不能像 C 语言那样使用 int |
| 花括号可省略 | 当代码块只有一条语句时可省略 {},但强烈不推荐 |
| 条件的副作用 | 条件表达式本身可能有副作用(如方法调用),请确保幂等性 |
示例:飞翔科技考勤预警
飞翔科技规定:员工当月迟到次数超过 3 次,系统自动发送警告通知。
/**
* 飞翔科技考勤预警——单分支 if
*/
public class AttendanceWarning {
public static void main(String[] args) {
String employeeName = "小崔";
int lateCount = 4; // 本月迟到次数
System.out.println("===== 飞翔科技考勤系统 =====");
System.out.println("员工:" + employeeName);
System.out.println("本月迟到:" + lateCount + " 次");
if (lateCount > 3) {
System.out.println("【警告】" + employeeName
+ ",你本月迟到已超过3次,请于本周五前提交情况说明!");
}
System.out.println("考勤检查完毕。");
}
}
运行输出:
===== 飞翔科技考勤系统 =====
员工:小崔
本月迟到:4 次
【警告】小崔,你本月迟到已超过3次,请于本周五前提交情况说明!
考勤检查完毕。
二、双分支 if-else
语法格式
if (布尔表达式) {
// 条件为 true 时执行
} else {
// 条件为 false 时执行
}
执行流程:条件为
true走 if 分支,为false走 else 分支。两条路径必走其一,且只走其一。
示例:绩效达标判断
飞翔科技季度绩效考核中,评分 >= 60 分为"达标",否则为"不达标"。
/**
* 飞翔科技绩效达标判断——双分支 if-else
*/
public class PerformanceCheck {
public static void main(String[] args) {
String name = "朱璐";
double score = 58.5; // 季度绩效评分
System.out.println("===== 飞翔科技绩效系统 =====");
System.out.println("员工:" + name);
System.out.println("绩效评分:" + score);
if (score >= 60.0) {
System.out.println("考核结果:达标 — 继续努力,争取优秀!");
} else {
System.out.println("考核结果:未达标 — 请与直属上级制定改进计划。");
}
System.out.println("绩效评定完成。");
}
}
运行输出:
===== 飞翔科技绩效系统 =====
员工:朱璐
绩效评分:58.5
考核结果:未达标 — 请与直属上级制定改进计划。
绩效评定完成。
三、多分支 if-else if-else
语法格式
if (条件1) {
// 条件1为 true
} else if (条件2) {
// 条件1为 false 且 条件2为 true
} else if (条件3) {
// 条件1、2均为 false 且 条件3为 true
} else {
// 以上条件全部为 false
}
执行流程:从上到下依次判断条件,第一个为
true的条件对应的代码块将被执行,后续条件不再判断。如果所有条件都不满足,执行else分支。
示例:飞翔科技职级薪资定级
飞翔科技技术序列职级(P1-P8),根据职级确定基础薪资范围:
/**
* 飞翔科技职级薪资定级——多分支 if-else if-else
*/
public class SalaryGrade {
public static void main(String[] args) {
String name = "小崔";
String level = "P5"; // 职级
System.out.println("===== 飞翔科技薪酬系统 =====");
System.out.println("员工:" + name + " | 职级:" + level);
int baseSalary;
if ("P1".equals(level) || "P2".equals(level)) {
baseSalary = 8000;
} else if ("P3".equals(level) || "P4".equals(level)) {
baseSalary = 15000;
} else if ("P5".equals(level) || "P6".equals(level)) {
baseSalary = 25000;
} else if ("P7".equals(level)) {
baseSalary = 40000;
} else if ("P8".equals(level)) {
baseSalary = 60000;
} else {
baseSalary = 0;
System.out.println("【错误】未知职级:" + level);
return;
}
System.out.println("基础月薪:¥" + baseSalary);
System.out.println("薪资定级完成。");
}
}
运行输出:
===== 飞翔科技薪酬系统 =====
员工:小崔 | 职级:P5
基础月薪:¥25000
薪资定级完成。
注意:这里使用
"P5".equals(level)而非level.equals("P5"),这是一种防御性编程——当level为null时前者返回false,后者抛出NullPointerException。
四、嵌套 if
语法格式
if (外层条件) {
if (内层条件1) {
// 外层true + 内层条件1 true
} else {
// 外层true + 内层条件1 false
}
} else {
// 外层 false
}
嵌套 if 本质上是将另一个 if-else 结构放在外层 if 的代码块中。每增加一层嵌套,代码复杂度呈指数增长。
示例:学生奖学金评定
飞翔科技与高校联合办学,奖学金评定规则如下:
- 总成绩 >= 90 且无挂科 → 一等奖学金(¥5000)
- 总成绩 >= 80 且无挂科 → 二等奖学金(¥3000)
- 总成绩 >= 70 且无挂科 → 三等奖学金(¥1000)
- 挂科 → 无奖学金
- 不满足上述 → 无奖学金
/**
* 飞翔科技联合办学奖学金评定——嵌套 if
*/
public class Scholarship {
public static void main(String[] args) {
String studentName = "小明";
double avgScore = 91.5; // 平均成绩
boolean hasFailed = false; // 是否有挂科
System.out.println("===== 飞翔科技奖学金评定 =====");
System.out.println("学生:" + studentName);
System.out.println("平均成绩:" + avgScore);
System.out.println("挂科情况:" + (hasFailed ? "有挂科" : "无挂科"));
if (hasFailed) {
// 外层:有挂科 → 直接淘汰
System.out.println("评定结果:无奖学金(存在挂科科目)");
} else {
// 外层:无挂科 → 进入内层成绩判断
if (avgScore >= 90) {
System.out.println("评定结果:一等奖学金 ¥5000");
} else if (avgScore >= 80) {
System.out.println("评定结果:二等奖学金 ¥3000");
} else if (avgScore >= 70) {
System.out.println("评定结果:三等奖学金 ¥1000");
} else {
System.out.println("评定结果:无奖学金(成绩未达标)");
}
}
System.out.println("评定流程结束。");
}
}
运行输出:
===== 飞翔科技奖学金评定 =====
学生:小明
平均成绩:91.5
挂科情况:无挂科
评定结果:一等奖学金 ¥5000
评定流程结束。
深度原理:if-else 的字节码实现
理解 if-else 在 JVM 层面的实现,可以帮助你写出更高效的代码,也能在面试中展现你的底层功底。
反编译工具
使用 javap -c 反编译 class 文件,查看字节码指令:
javac IfElseDemo.java
javap -c -p IfElseDemo
关键字节码指令
| 字节码指令 | 操作数 | 含义 | 类比 |
|---|---|---|---|
ifeq | 偏移量 | 栈顶 int 值等于 0 时跳转 | if (value == 0) |
ifne | 偏移量 | 栈顶 int 值不等于 0 时跳转 | if (value != 0) |
iflt | 偏移量 | 栈顶 int 值小于 0 时跳转 | if (value < 0) |
ifge | 偏移量 | 栈顶 int 值大于等于 0 时跳转 | if (value >= 0) |
ifgt | 偏移量 | 栈顶 int 值大于 0 时跳转 | if (value > 0) |
ifle | 偏移量 | 栈顶 int 值小于等于 0 时跳转 | if (value <= 0) |
if_icmpeq | 偏移量 | 比较栈顶两个 int 值,相等时跳转 | if (a == b) |
if_icmpne | 偏移量 | 比较栈顶两个 int 值,不等时跳转 | if (a != b) |
if_icmplt | 偏移量 | 比较栈顶两个 int 值,小于时跳转 | if (a < b) |
if_icmpge | 偏移量 | 比较栈顶两个 int 值,大于等于时跳转 | if (a >= b) |
if_icmpgt | 偏移量 | 比较栈顶两个 int 值,大于时跳转 | if (a > b) |
if_icmple | 偏移量 | 比较栈顶两个 int 值,小于等于时跳转 | if (a <= b) |
if_acmpeq | 偏移量 | 比较两个引用是否指向同一对象 | if (obj1 == obj2) |
if_acmpne | 偏移量 | 比较两个引用是否指向不同对象 | if (obj1 != obj2) |
goto | 偏移量 | 无条件跳转 | 跳过 else 分支 |
ifnull | 偏移量 | 引用为 null 时跳转 | if (obj == null) |
ifnonnull | 偏移量 | 引用不为 null 时跳转 | if (obj != null) |
示例:简单 if-else 的字节码分析
Java 源码:
public static int max(int a, int b) {
if (a > b) {
return a;
} else {
return b;
}
}
使用 javap -c 反编译后的字节码:
public static int max(int, int);
Code:
0: iload_0 // 将参数 a 压入操作数栈
1: iload_1 // 将参数 b 压入操作数栈
2: if_icmple 7 // 比较 a 和 b,如果 a <= b 跳转到偏移量 7
5: iload_0 // a > b:将 a 压栈准备返回
6: ireturn // 返回 a
7: iload_1 // a <= b:将 b 压栈准备返回
8: ireturn // 返回 b
关键发现:
- 条件取反:Java 源码是
if (a > b),但字节码使用if_icmple(a <= b 时跳转)。这是因为字节码的跳转逻辑是"条件满足时跳过 if 代码块",必须取反。 - goto 省略:当 if 和 else 都以 return 结束时,else 分支天然是 fall-through,不需要显式 goto。
- 性能提示:简单的 if-else 在 JIT 编译后可能被优化为条件传送指令(cmov),消除分支预测开销。
易错场景
反例一:误用 == 比较字符串
// ❌ 错误:用 == 比较字符串内容
String dept = new String("技术部");
if (dept == "技术部") {
System.out.println("技术部员工");
}
// 输出:(无输出,因为 == 比较的是引用地址)
原因分析:== 比较的是栈中的引用值(内存地址),而不是堆中字符串的实际内容。new String("技术部") 创建了一个新对象,其地址与字符串常量池中的 "技术部" 不同。
// ✅ 正确:使用 equals() 比较内容
String dept = "技术部";
if ("技术部".equals(dept)) {
System.out.println("明确是技术部员工");
}
反例二:条件顺序导致的"死分支"
小崔在写薪资判断逻辑时,不小心写出了永远不会被执行的 else if 分支:
// ❌ 错误:条件顺序不当,"死分支"永远不会执行
double salary = 50000;
if (salary > 10000) {
System.out.println("高薪");
} else if (salary > 50000) { // 永远不可能走到这里!
System.out.println("超高薪"); // 死分支
}
// salary=50000 时先匹配第一个条件,打印"高薪"
纠正:多分支的条件应该从最严格或最特殊的开始排列。
// ✅ 正确:从大到小排列条件
double salary = 50000;
if (salary > 50000) {
System.out.println("超高薪");
} else if (salary > 10000) {
System.out.println("高薪");
} else {
System.out.println("普通薪资");
}
反例一:省略花括号导致的歧义
// ❌ 错误:省略花括号,缩进具有误导性
int score = 85;
String grade = "B";
if (score >= 90)
grade = "A";
System.out.println("优秀"); // 这条语句不受 if 控制!
// 无论 score 是多少,都会打印"优秀"
C/C++ 程序员容易踩这个坑。Java 编译器不会报错——代码完全合法,只是逻辑错了。
// ✅ 正确:始终使用花括号
int score = 85;
String grade = "B";
if (score >= 90) {
grade = "A";
System.out.println("优秀");
}
反例一:浮点数直接比较
// ❌ 错误:浮点数直接比较可能因精度问题失败
double price = 0.1 + 0.2; // 实际值:0.30000000000000004
if (price == 0.3) {
System.out.println("价格正确");
}
// 输出:(无输出)
// ✅ 正确:使用容差比较
double price = 0.1 + 0.2;
double EPSILON = 1e-10;
if (Math.abs(price - 0.3) < EPSILON) {
System.out.println("价格正确(在误差范围内)");
}
反例一:空指针在条件判断中
// ❌ 错误:未判空直接调用方法
String level = null;
if (level.equals("P5")) { // NullPointerException!
System.out.println("职级 P5");
}
// ✅ 正确:常量在前,或先判空
String level = null;
if ("P5".equals(level)) { // 安全,返回 false 而非抛异常
System.out.println("职级 P5");
}
// 或者
if (level != null && level.equals("P5")) {
System.out.println("职级 P5");
}
面试考点
Q1:if-else if-else 的匹配原则是什么?else 与哪个 if 配对?
悬空 else 问题:else 总是与最近的、尚未配对的 if 配对(最近匹配原则)。编译器严格按代码结构匹配,不受缩进影响。
if (a > 0) if (b > 0) System.out.println("1"); else // 这个 else 与 if (b > 0) 配对,而非 if (a > 0) System.out.println("2");因此,永远使用花括号,即使只有一行代码。
Q2:if-else 和三元运算符(? :)如何选择?
如果两个分支都是给同一个变量赋值,优先使用三元运算符,代码更简洁:
// 推荐:三元运算符 String status = (score >= 60) ? "通过" : "未通过"; // 不推荐:if-else 做同样的事 String status; if (score >= 60) { status = "通过"; } else { status = "未通过"; }但三元运算符不可嵌套超过一层,否则严重降低可读性。
Q3:为什么 Java 的 if 条件必须返回 boolean,而 C 语言可以接受 int?
这是 Java 设计者有意为之——为了类型安全。在 C 语言中
if (1)合法,if (a = 5)(赋值而非比较)也是合法的且极易引发 Bug。Java 强制要求 boolean 类型,从编译器层面杜绝了这类错误。字节码层面:JVM 没有专门的 boolean 类型,boolean 在编译后被映射为 int(0 表示 false,1 表示 true),但编译器会强制执行类型检查。
Q4:if-else 和 switch 如何选择?
维度 if-else switch 判断条件 任意布尔表达式 仅支持等值判断 分支数量 适合少量分支(≤3) 适合较多分支(≥4) 性能 O(n) 线性判断 tableswitch 实现 O(1) 可读性 复杂条件时更好 等值判断时更清晰 规则:条件为等值判断且分支数较多时用 switch,其余情况用 if-else。
小结
| 要点 | 说明 |
|---|---|
| if 条件必须是 boolean 类型 | 不能用 int 或其它类型 |
| 多分支条件顺序很重要 | 最特殊/最严格的条件排前面 |
始终使用花括号 {} | 避免悬空 else 和歧义 |
字符串比较用 equals() | == 比较的是引用地址 |
| 嵌套 if 不要超过 3 层 | 超过则考虑提前 return 或策略模式重构 |
| 字节码取反实现分支跳转 | 理解 if_icmple 等指令有助于读懂反编译结果 |