哈喽大家好!今天咱们来聊聊Java里的选择结构。说实话,这玩意儿虽然是编程基础中的基础,但我发现很多人用了好几年还是只会if-else硬堆,代码写得那叫一个”壮观”。今天我就把这些年踩过的坑、总结的经验都分享出来,保证让你对选择结构有个全新的认识。
选择结构是个啥?
咱们先从最本质的东西说起。程序不可能一条路走到黑,总得根据不同情况做不同的事儿吧?比如用户登录,密码对了进系统,错了就提示重新输入。这就是选择结构的作用——让程序具备”决策能力”。
用大白话说,选择结构就是”如果…那么…否则…”的逻辑。你每天都在用这种思维方式,只是可能没意识到而已。
单分支if:最简单的选择
这是最基础的形式,就一个判断条件。
int age = 17;
if (age < 18) {
System.out.println("未成年人,禁止进入");
}
看着简单吧?但这里有个细节很多人会忽略——大括号要不要写?
// 可以不写大括号
if (age < 18)
System.out.println("未成年人,禁止进入");
// 但强烈建议写上!
if (age < 18) {
System.out.println("未成年人,禁止进入");
}
为什么建议写?因为不写大括号只有紧跟着的第一行代码受if控制。不然就可能遇到这样的Bug:
if (isVip)
discount = 0.8;
System.out.println("享受VIP折扣"); // 这行不管isVip是啥都会执行!
看出问题了吗?第二行代码其实不在if的控制范围内,无论是不是VIP都会打印。这种Bug特别隐蔽,养成写大括号的习惯能省不少麻烦。
双分支if-else:非此即彼
当你有两种互斥的情况时,就该if-else上场了。
int score = 75;
if (score >= 60) {
System.out.println("恭喜你,考试通过了!");
} else {
System.out.println("很遗憾,需要补考");
}
实战场景:判断奇偶数
public String checkEvenOdd(int number) {
if (number % 2 == 0) {
return "这是偶数";
} else {
return "这是奇数";
}
}
这种情况其实还可以用三元运算符简化,后面会讲到。
小心逻辑陷阱
我见过不少新手写出这样的代码:
boolean hasTicket = true;
if (hasTicket == true) { // 多此一举!
System.out.println("可以进场");
}
hasTicket本身就是布尔值,直接写if (hasTicket)就行了。同理,if (hasTicket == false)应该写成if (!hasTicket)。
多分支if-else if-else:层层筛选
当有多个条件需要判断时,就得用到这个结构了。
int score = 85;
if (score >= 90) {
System.out.println("优秀,奖励现金500元");
} else if (score >= 80) {
System.out.println("良好,奖励现金300元");
} else if (score >= 60) {
System.out.println("及格,继续努力");
} else {
System.out.println("不及格,需要补考");
}
这里有个重要的执行逻辑:从上往下判断,只要有一个条件满足就执行对应代码块,然后跳出整个结构。所以score=85时,只会打印”良好”那句,不会再去判断后面的条件。
条件的顺序很重要!
我之前就遇到过这样一个坑,写的代码逻辑完全反了:
// 错误示范!
if (score >= 60) {
System.out.println("及格");
} else if (score >= 80) { // 这个条件永远不会执行到
System.out.println("良好");
} else if (score >= 90) { // 这个也不会
System.out.println("优秀");
}
为啥?因为score>=80的时候,早就满足score>=60了,直接在第一个if里就返回了。所以多分支判断一定要从严格到宽松排序,或者从大到小、从小到大,总之得有个明确的顺序。
实战案例:计算个税
import java.util.Scanner;
public class Main {
public static double calculateTax(double income) {
double tax = 0;
double taxableIncome = income - 5000; // 应纳税所得额(扣除免征额5000)
if (taxableIncome <= 0) {
tax = 0; // 免征额以下,无税
} else if (taxableIncome <= 3000) {
tax = taxableIncome * 0.03; // 3%税率
} else if (taxableIncome <= 12000) {
tax = taxableIncome * 0.1 - 210; // 10%税率
} else if (taxableIncome <= 25000) {
tax = taxableIncome * 0.2 - 1410; // 20%税率
} else {
tax = taxableIncome * 0.25 - 2660; // 25%税率
}
return tax;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("请输入收入金额:");
double income = sc.nextDouble();
double tax = calculateTax(income);
System.out.printf("应缴纳税费:%.2f 元%n", tax); // 保留2位小数
sc.close();
}
}
这个例子展示了如何用if-else if结构处理分段计算问题。实际工作中这种场景特别多,比如快递费计算、会员等级判定等等。
嵌套if:处理复杂逻辑
有时候判断条件不是线性的,需要在一个条件成立后再判断另一个条件。
int age = 25;
boolean hasLicense = true;
if (age >= 18) {
if (hasLicense) {
System.out.println("可以开车上路");
} else {
System.out.println("年龄够了,但需要先考驾照");
}
} else {
System.out.println("年龄不够,不能开车");
}
嵌套if很容易写得乱七八糟,特别是嵌套层次多了以后。我的建议是:
1. 控制嵌套层次,最好不超过3层
嵌套太深代码可读性会变得很差。如果逻辑确实复杂,考虑把部分判断提取成方法。
2. 能用逻辑运算符就用逻辑运算符
上面那个例子其实可以简化:
if (age >= 18 && hasLicense) {
System.out.println("可以开车上路");
} else if (age >= 18 && !hasLicense) {
System.out.println("年龄够了,但需要先考驾照");
} else {
System.out.println("年龄不够,不能开车");
}
看着是不是清晰多了?
3. 善用卫语句(Guard Clauses)
什么是卫语句?就是把不符合条件的情况先排除掉。
// 传统写法
public void processOrder(Order order) {
if (order != null) {
if (order.isValid()) {
if (order.getAmount() > 0) {
// 处理订单...
}
}
}
}
// 使用卫语句
public void processOrder(Order order) {
if (order == null) return;
if (!order.isValid()) return;
if (order.getAmount() <= 0) return;
// 处理订单...
}
第二种写法是不是顺眼多了?这种写法减少了嵌套层次,让主流程逻辑更清晰。这个技巧可以从《重构》那本书中学到,超级实用。
switch-case:多分支的优雅解决方案
当你要根据一个变量的不同值执行不同代码时,switch比一堆if-else if清晰多了。
基础用法
int dayOfWeek = 3;
switch (dayOfWeek) {
case 1:
System.out.println("星期一,打工人的煎熬开始了");
break;
case 2:
System.out.println("星期二,继续加油");
break;
case 3:
System.out.println("星期三,过半了");
break;
case 4:
System.out.println("星期四,快到周末了");
break;
case 5:
System.out.println("星期五,终于要解放了");
break;
case 6:
case 7:
System.out.println("周末,开心!");
break;
default:
System.out.println("这是什么日子?");
}
注意几个要点:
1. break很重要!
如果不写break,代码会继续往下执行(这叫”fall through”)。有时候这是我们想要的效果(比如上面case 6和case 7共用一段代码),但更多时候忘记写break是Bug。
int month = 3;
switch (month) {
case 3:
System.out.println("三月");
// 忘记写break了!
case 4:
System.out.println("四月");
break;
}
// 输出:三月 四月(两行都打印了!)
一定要养成习惯,写完case就立马写break,不然因为这个就得Debug半天。
2. default是可选的,但建议写上
default相当于其他语言里的else,处理所有没匹配到的情况。就算你觉得已经覆盖了所有可能,也建议写个default,哪怕只是打个日志。这样将来代码出问题时,至少能知道走到了意外分支。
switch支持的数据类型
Java的switch支持这些类型:
- byte、short、int、char(基本类型)
- Byte、Short、Integer、Character(包装类)
- String(Java 7开始支持)
- enum(枚举类型)
// String类型的switch
String command = "start";
switch (command) {
case "start":
System.out.println("启动服务");
break;
case "stop":
System.out.println("停止服务");
break;
case "restart":
System.out.println("重启服务");
break;
default:
System.out.println("未知命令");
}
注意:switch不支持long类型!这是个常见的坑。如果要判断long类型的值,老老实实用if-else。
实战:枚举类型配合switch
这是我认为switch最优雅的用法之一:
public enum OrderStatus {
PENDING, // 待支付
PAID, // 已支付
SHIPPED, // 已发货
DELIVERED, // 已送达
CANCELLED // 已取消
}
public void handleOrder(Order order) {
switch (order.getStatus()) {
case PENDING:
System.out.println("订单待支付,发送支付提醒");
break;
case PAID:
System.out.println("订单已支付,准备发货");
break;
case SHIPPED:
System.out.println("订单已发货,更新物流信息");
break;
case DELIVERED:
System.out.println("订单已送达,请用户确认收货");
break;
case CANCELLED:
System.out.println("订单已取消,处理退款");
break;
default:
throw new IllegalStateException("未知的订单状态");
}
}
枚举+switch的组合既安全又清晰,而且如果你加了新的枚举值但忘记在switch里处理,编译器会给你警告。
Java 12+的新特性:switch表达式
如果你用的是Java 12及以上版本,那你有福了!新的switch表达式简直不要太香。
箭头语法
int dayOfWeek = 3;
String dayType = switch (dayOfWeek) {
case 1, 2, 3, 4, 5 -> "工作日";
case 6, 7 -> "休息日";
default -> "无效日期";
};
System.out.println(dayType);
看到没?用箭头->替代了冒号和break,而且可以直接返回值!这个语法有几个好处:
- 不需要break:箭头语法自动break,不会fall through
- 可以直接赋值:switch本身可以作为表达式返回值
- 支持多个值:用逗号分隔,比写多个case简洁
yield关键字
如果switch分支里需要多行代码,就用yield返回值:
int score = 85;
String grade = switch (score / 10) {
case 10, 9 -> "A";
case 8 -> "B";
case 7 -> "C";
case 6 -> "D";
default -> {
System.out.println("成绩不及格");
yield "F"; // 用yield返回值
}
};
这个新语法真的让代码简洁了不少,而且更不容易出错。如果你的项目用的Java版本支持,强烈建议试试。
三元运算符:单行的if-else
对于简单的二选一逻辑,三元运算符是最简洁的写法。
int age = 20;
String result = (age >= 18) ? "成年人" : "未成年人";
等价于:
String result;
if (age >= 18) {
result = "成年人";
} else {
result = "未成年人";
}
什么时候用三元运算符?
我的原则是:逻辑简单时用,复杂了就别用。
// 可以用
int max = (a > b) ? a : b;
// 最好别用(嵌套三元运算符)
String result = (score >= 90) ? "优秀" :
(score >= 80) ? "良好" :
(score >= 60) ? "及格" : "不及格";
嵌套的三元运算符可读性太差了,还不如老老实实写if-else if。记住,代码是给人看的,简洁不等于炫技。
一个实用技巧:避免空指针
// 传统写法
String name;
if (user != null) {
name = user.getName();
} else {
name = "匿名用户";
}
// 三元运算符
String name = (user != null) ? user.getName() : "匿名用户";
// 更优雅的写法(Java 8+)
String name = Optional.ofNullable(user)
.map(User::getName)
.orElse("匿名用户");
第三种用Optional的写法最安全,不过这是另一个话题了,有机会单独写一篇。
实战技巧:如何写出好的选择结构
技巧1:提前return,减少嵌套
// 不好的写法
public void processUser(User user) {
if (user != null) {
if (user.isActive()) {
if (user.hasPermission()) {
// 真正的业务逻辑
doSomething();
} else {
System.out.println("权限不足");
}
} else {
System.out.println("用户未激活");
}
} else {
System.out.println("用户不存在");
}
}
// 好的写法
public void processUser(User user) {
if (user == null) {
System.out.println("用户不存在");
return;
}
if (!user.isActive()) {
System.out.println("用户未激活");
return;
}
if (!user.hasPermission()) {
System.out.println("权限不足");
return;
}
// 真正的业务逻辑
doSomething();
}
第二种写法主流程一目了然,异常情况先处理掉,这就是所谓的”卫语句”模式。
技巧2:用策略模式替代大量if-else
当if-else分支特别多时,考虑用设计模式优化:
// 不好的写法
public double calculatePrice(String memberType, double amount) {
if ("普通会员".equals(memberType)) {
return amount * 0.95;
} else if ("白银会员".equals(memberType)) {
return amount * 0.90;
} else if ("黄金会员".equals(memberType)) {
return amount * 0.85;
} else if ("铂金会员".equals(memberType)) {
return amount * 0.80;
} else {
return amount;
}
}
// 用策略模式
interface DiscountStrategy {
double calculate(double amount);
}
Map<String, DiscountStrategy> strategies = new HashMap<>();
strategies.put("普通会员", amount -> amount * 0.95);
strategies.put("白银会员", amount -> amount * 0.90);
strategies.put("黄金会员", amount -> amount * 0.85);
strategies.put("铂金会员", amount -> amount * 0.80);
public double calculatePrice(String memberType, double amount) {
return strategies.getOrDefault(memberType, amount -> amount)
.calculate(amount);
}
这样扩展起来特别方便,加个新会员类型就是往Map里加一项,不用改原来的代码。
技巧3:把复杂条件提取成方法
// 不好的写法
if (user.getAge() >= 18 && user.hasLicense() &&
!user.hasDrunkDriving() && user.getCarInsurance() != null) {
allowDriving();
}
// 好的写法
if (canDrive(user)) {
allowDriving();
}
private boolean canDrive(User user) {
return user.getAge() >= 18
&& user.hasLicense()
&& !user.hasDrunkDriving()
&& user.getCarInsurance() != null;
}
给条件起个有意义的方法名,代码可读性立马提升。而且以后修改判断逻辑也方便,只改一个地方。
常见坑点总结
坑1:浮点数的相等判断
double price = 0.1 + 0.2;
if (price == 0.3) { // 错误!这个条件可能不成立
System.out.println("价格正确");
}
// 正确的做法
if (Math.abs(price - 0.3) < 0.00001) {
System.out.println("价格正确");
}
浮点数有精度问题,直接用==判断会出问题。这是计算机底层表示浮点数的方式决定的。
坑2:字符串比较用equals
String input = new String("hello");
if (input == "hello") { // 错误!比较的是引用
System.out.println("相等");
}
// 正确的做法
if ("hello".equals(input)) { // 把常量放前面,避免空指针
System.out.println("相等");
}
字符串比较要用equals方法,用==比较的是引用地址。而且把常量放前面是个好习惯,即使input是null也不会报空指针异常。
坑3:if里不要有赋值操作
int x = 10;
if (x = 5) { // 编译错误!Java不允许这样写
System.out.println("x等于5");
}
幸好Java会报编译错误,不像C语言那样会导致逻辑Bug。但还是要注意,判断相等用==,赋值用=。
坑4:注意运算符优先级
boolean result = true;
if (!result == false) { // 这个条件永远不成立!
System.out.println("结果为假");
}
// 正确的写法
if (result == false) { // 或者直接写 !result
System.out.println("结果为假");
}
!的优先级高于==,所以!result == false实际上是(!result) == false,也就是false == false,结果是true。遇到复杂表达式,多加括号总没错。
写在最后
选择结构虽然是基础,但要写好还真不容易。我的建议是:
- 保持代码简洁:能用简单的if就别用复杂的嵌套
- 注重可读性:给条件起好名字,复杂逻辑提取成方法
- 避免重复:相似的判断逻辑考虑复用
- 灵活运用:根据场景选择if、switch还是三元运算符
编程没有银弹,很多时候要靠经验判断。多写、多思考、多重构,慢慢就能写出优雅的代码了。
评论留言
欢迎您,!您可以在这里畅言您的的观点与见解!