1. Java分支结构概述
在Java编程中,分支结构是控制程序执行流程的基础工具之一。就像交通信号灯指挥车辆行驶方向一样,分支结构让程序能够根据不同的条件做出决策,选择不同的执行路径。这种能力使得程序不再是简单的线性执行,而具备了"思考"和"选择"的能力。
1.1 为什么需要分支结构
想象你正在编写一个学生成绩管理系统。当输入一个分数时,程序需要判断这个分数属于哪个等级(优秀、良好、及格或不及格)。如果没有分支结构,你只能编写一个只能处理单一情况的程序,这显然不符合实际需求。分支结构让程序能够:
- 根据不同的输入做出不同的响应
- 处理多种可能的情况
- 在运行时动态决定执行路径
- 构建更复杂、更智能的业务逻辑
1.2 分支结构的基本类型
Java提供了两种主要的分支结构:
- if语句:适用于基于布尔条件的判断,灵活性高
- switch语句:适用于基于离散值的多路分支,结构清晰
这两种结构各有特点,适用于不同的场景。理解它们的区别和适用情况是编写高效、可读性强的Java代码的关键。
2. if分支结构详解
if语句是Java中最基础也是最灵活的分支结构。它允许程序根据条件的真假来决定是否执行特定的代码块。if语句有几种变体,可以满足不同复杂度的条件判断需求。
2.1 单分支if语句
最基本的if语句形式只包含一个条件和一个执行块:
java复制if (条件表达式) {
// 条件为true时执行的代码
}
关键点:
- 条件表达式必须返回布尔值(true或false)
- 如果只有一条语句,花括号可以省略,但为了代码清晰和避免错误,建议始终使用花括号
- 条件表达式可以包含比较运算符(>, <, ==等)、逻辑运算符(&&, ||, !)等
实际案例:
java复制int age = 20;
if (age >= 18) {
System.out.println("您已成年,可以进入");
}
2.2 双分支if-else语句
当需要在条件为true和false时分别执行不同的代码块时,可以使用if-else结构:
java复制if (条件表达式) {
// 条件为true时执行的代码
} else {
// 条件为false时执行的代码
}
实际案例:
java复制int score = 75;
if (score >= 60) {
System.out.println("考试通过");
} else {
System.out.println("考试未通过");
}
2.3 多分支if-else if-else语句
对于需要检查多个条件的情况,可以使用if-else if-else结构:
java复制if (条件1) {
// 条件1为true时执行的代码
} else if (条件2) {
// 条件2为true时执行的代码
} else if (条件3) {
// 条件3为true时执行的代码
} else {
// 所有条件都为false时执行的代码
}
实际案例:
java复制int temperature = 28;
if (temperature > 30) {
System.out.println("天气炎热");
} else if (temperature > 20) {
System.out.println("天气温暖");
} else if (temperature > 10) {
System.out.println("天气凉爽");
} else {
System.out.println("天气寒冷");
}
2.4 嵌套if语句
if语句可以嵌套使用,即在if或else的代码块中包含另一个if语句:
java复制if (条件1) {
if (条件2) {
// 条件1和条件2都为true时执行的代码
} else {
// 条件1为true但条件2为false时执行的代码
}
} else {
// 条件1为false时执行的代码
}
实际案例:
java复制boolean hasTicket = true;
int age = 16;
if (hasTicket) {
if (age >= 18) {
System.out.println("可以观看所有电影");
} else {
System.out.println("只能观看适合未成年人的电影");
}
} else {
System.out.println("请先购票");
}
2.5 if语句的注意事项
-
条件表达式的准确性:确保条件表达式能够准确反映你的意图。常见的错误包括:
- 使用赋值运算符(=)而不是相等运算符(==)
- 逻辑运算符使用不当
- 边界条件处理不当
-
代码块的花括号:即使只有一条语句,也建议使用花括号。这可以避免因后续修改代码而引入错误。
-
代码可读性:
- 避免过深的嵌套(一般不超过3层)
- 复杂的条件表达式可以拆分成多个变量或方法
- 为条件表达式添加注释说明业务含义
-
性能考虑:
- 将最可能为true的条件放在前面
- 避免重复计算相同的表达式
3. switch分支结构详解
switch语句提供了一种基于表达式的值来选择不同执行路径的方式。它特别适合处理有多个离散选项的情况,可以使代码更加清晰和易于维护。
3.1 基本语法结构
java复制switch (表达式) {
case 值1:
// 代码块1
break;
case 值2:
// 代码块2
break;
// 更多case...
default:
// 默认代码块
}
3.2 switch语句的执行流程
- 计算switch表达式的值
- 将表达式的值与每个case标签的值进行比较
- 如果找到匹配的case标签,执行对应的代码块
- 如果没有匹配的case标签,执行default代码块(如果存在)
- 遇到break语句时退出整个switch结构
3.3 switch语句的实际应用
示例1:处理星期几
java复制int dayOfWeek = 3;
switch (dayOfWeek) {
case 1:
System.out.println("星期一");
break;
case 2:
System.out.println("星期二");
break;
case 3:
System.out.println("星期三");
break;
// 其他case...
default:
System.out.println("无效的星期数");
}
示例2:处理枚举类型
java复制enum Color { RED, GREEN, BLUE }
Color color = Color.GREEN;
switch (color) {
case RED:
System.out.println("红色");
break;
case GREEN:
System.out.println("绿色");
break;
case BLUE:
System.out.println("蓝色");
break;
}
3.4 switch语句的特性
-
case穿透:如果没有break语句,执行会从一个case继续到下一个case。这有时是有意为之的,但大多数情况下应该避免。
-
default分支:是可选的,用于处理所有case都不匹配的情况。
-
支持的类型:
- Java 7之前:byte, short, char, int
- Java 7+:String
- 枚举类型
-
case标签:
- 必须是常量表达式
- 不能有重复的case值
- 可以是字面量或final常量
3.5 switch语句的注意事项
-
不要忘记break语句:除非有意利用case穿透特性,否则每个case块结束时都应该有break语句。
-
default分支的使用:即使你认为所有情况都已覆盖,也建议保留default分支来处理意外情况。
-
代码组织:
- 将最常发生的case放在前面
- 相关的case可以组织在一起
- 复杂的case逻辑可以提取到方法中
-
Java 12+的新特性:从Java 12开始,switch表达式有了更简洁的语法(使用箭头符号和yield关键字),但在本文中我们主要讨论传统语法。
4. if与switch的选择策略
在实际开发中,选择使用if语句还是switch语句是一个常见的决策。正确的选择可以使代码更清晰、更高效,也更易于维护。
4.1 使用if语句的场景
-
条件判断基于范围:例如分数等级划分、年龄分段等
java复制if (score >= 90) { grade = 'A'; } else if (score >= 80) { grade = 'B'; } -
条件涉及复杂的布尔表达式:需要与、或、非等逻辑运算
java复制if (age > 18 && hasLicense && !isDrunk) { allowDriving(); } -
条件判断基于对象类型或状态:例如多态行为的选择
java复制if (animal instanceof Dog) { ((Dog)animal).bark(); } else if (animal instanceof Cat) { ((Cat)animal).meow(); } -
条件数量较少或不确定:当分支数量少或可能在运行时变化时
4.2 使用switch语句的场景
-
基于单个变量的离散值选择:例如菜单选择、状态机处理
java复制switch (userChoice) { case 1: startGame(); break; case 2: loadGame(); break; case 3: showHelp(); break; } -
需要清晰列出所有可能选项:使代码更易于阅读和维护
java复制switch (errorCode) { case 404: handleNotFound(); break; case 500: handleServerError(); break; // 其他错误代码... } -
多个选项共享相同处理逻辑:利用case穿透特性
java复制switch (month) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: days = 31; break; case 4: case 6: case 9: case 11: days = 30; break; case 2: days = isLeapYear ? 29 : 28; break; } -
处理枚举类型:与枚举配合使用非常自然
java复制switch (direction) { case NORTH: moveUp(); break; case EAST: moveRight(); break; // 其他方向... }
4.3 性能考虑
在大多数情况下,if和switch的性能差异可以忽略不计。但在某些特定场景下:
-
switch的优势:
- 当case数量较多时(通常5个以上),switch可能更高效
- JVM对switch有特殊优化,特别是对于密集的case值
-
if的优势:
- 对于简单的条件判断,if语句可能更直接
- 当条件判断非常不均衡(某些条件远比其他条件常见)时,if语句可以通过条件排序来优化
4.4 代码可读性比较
-
switch的优点:
- 当基于同一个变量的不同值进行分支时,结构更清晰
- 可以明确列出所有可能的选项
- 便于后续添加新的case
-
if的优点:
- 对于复杂的条件表达式更灵活
- 可以处理基于不同变量的条件
- 嵌套结构可以表达更复杂的逻辑关系
4.5 重构建议
在某些情况下,当分支逻辑变得复杂时,可以考虑以下重构方式:
- 用多态替代条件语句:特别是当分支基于对象类型时
- 使用策略模式:将不同分支的逻辑封装到不同的策略类中
- 使用枚举+方法:将分支逻辑放到枚举的方法中
- 使用Map+函数式接口:Java 8+可以使用Map来存储处理函数
5. 常见问题与最佳实践
在实际开发中使用分支结构时,开发者经常会遇到一些典型问题和陷阱。了解这些问题并掌握相应的最佳实践,可以显著提高代码质量和开发效率。
5.1 常见错误与陷阱
-
赋值(=)与相等(==)混淆
java复制if (x = 5) { // 错误:这是赋值,不是比较 // ... }解决方法:常量放在左边可以避免这种错误
java复制if (5 == x) { // 如果误写为5 = x会编译错误 // ... } -
switch语句中遗漏break
java复制switch (x) { case 1: System.out.println("One"); // 忘记break case 2: System.out.println("Two"); break; }解决方法:始终注意添加break,除非有意使用case穿透
-
浮点数比较
java复制double a = 0.1 + 0.2; if (a == 0.3) { // 可能不成立,因为浮点精度问题 // ... }解决方法:使用误差范围比较
java复制if (Math.abs(a - 0.3) < 0.00001) { // ... } -
null检查遗漏
java复制String str = possiblyNullMethod(); if (str.equals("value")) { // 可能抛出NullPointerException // ... }解决方法:将字面量放在前面或显式检查null
java复制if ("value".equals(str)) { // ... }
5.2 代码风格建议
-
花括号使用:即使只有一条语句也使用花括号
java复制// 不推荐 if (condition) doSomething(); // 推荐 if (condition) { doSomething(); } -
条件表达式格式化:复杂条件适当换行和缩进
java复制if (user != null && user.isActive() && (user.hasRole("admin") || user.hasPermission("edit"))) { // ... } -
避免深层嵌套:深层嵌套会降低可读性
java复制// 不推荐 if (condition1) { if (condition2) { if (condition3) { // ... } } } // 推荐:使用早期返回或卫语句 if (!condition1) return; if (!condition2) return; if (!condition3) return; // ... -
switch语句的default处理:即使你认为不需要也保留default
java复制switch (x) { // cases... default: throw new IllegalArgumentException("Unexpected value: " + x); }
5.3 性能优化技巧
-
条件排序:将最可能为true的条件放在前面
java复制if (mostLikelyCondition) { // ... } else if (lessLikelyCondition) { // ... } -
避免重复计算:将重复使用的表达式结果保存到变量
java复制// 不推荐 if (expensiveOperation() > threshold && expensiveOperation() < max) { // ... } // 推荐 int result = expensiveOperation(); if (result > threshold && result < max) { // ... } -
使用switch处理多路分支:当分支较多时,switch通常比if-else链更高效
-
考虑使用查找表:对于某些情况,可以用数组或Map替代分支语句
java复制// 使用数组替代switch String[] messages = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; String day = messages[dayOfWeek - 1];
5.4 测试与调试建议
-
边界条件测试:特别注意测试边界值情况
java复制// 测试边界 if (score >= 90) { // 测试89, 90, 91 // ... } -
覆盖所有分支:确保测试用例覆盖所有可能的分支路径
-
null值测试:验证代码对null输入的处理是否正确
-
日志记录:在复杂分支处添加日志,帮助调试
java复制if (complexCondition) { logger.debug("Condition met because..."); // ... }
5.5 现代Java中的改进
-
switch表达式(Java 12+):
java复制String dayType = switch (day) { case "Sat", "Sun" -> "Weekend"; case "Mon", "Tue", "Wed", "Thu", "Fri" -> "Weekday"; default -> throw new IllegalArgumentException("Invalid day: " + day); }; -
模式匹配(Java 16+预览特性):
java复制if (obj instanceof String s) { // 可以直接使用s System.out.println(s.length()); } -
记录类(Java 16+):可以与switch结合使用,简化数据类的处理
6. 实际应用案例分析
为了更好地理解分支结构的实际应用,让我们通过几个完整的案例来分析if和switch在实际项目中的使用方式。这些案例来自常见的业务场景,可以帮助我们掌握如何在实际开发中合理运用分支结构。
6.1 案例一:用户权限控制系统
需求描述:根据用户的角色和权限级别,决定用户可以访问哪些资源。角色分为:游客(GUEST)、普通用户(USER)、管理员(ADMIN)和超级管理员(SUPER_ADMIN)。
实现方案:
java复制public class AccessControl {
public static void checkAccess(User user, Resource resource) {
if (user == null) {
throw new IllegalArgumentException("用户不能为null");
}
if (resource == null) {
throw new IllegalArgumentException("资源不能为null");
}
// 先检查用户是否被禁用
if (user.isDisabled()) {
System.out.println("访问拒绝:用户已被禁用");
return;
}
// 根据角色进行权限检查
switch (user.getRole()) {
case "GUEST":
if (resource.isPublic()) {
System.out.println("允许游客访问公共资源");
} else {
System.out.println("访问拒绝:游客只能访问公共资源");
}
break;
case "USER":
if (resource.isPublic() || resource.isUserAccessible()) {
System.out.println("允许普通用户访问");
} else {
System.out.println("访问拒绝:权限不足");
}
break;
case "ADMIN":
if (!resource.isAdminRestricted()) {
System.out.println("允许管理员访问");
} else {
System.out.println("访问拒绝:管理员无此资源权限");
}
break;
case "SUPER_ADMIN":
System.out.println("允许超级管理员访问所有资源");
break;
default:
System.out.println("访问拒绝:未知用户角色");
}
}
}
代码分析:
- 使用if语句进行前置条件检查和简单条件判断
- 使用switch语句处理基于用户角色的多路分支
- 在每个case内部再根据具体情况进行更细致的权限检查
- 使用了default处理未知角色情况
6.2 案例二:电商平台折扣计算系统
需求描述:根据用户的会员等级和当前促销活动,计算商品折扣。会员等级有:普通(NORMAL)、银牌(SILVER)、金牌(GOLD)、钻石(DIAMOND)。促销活动有:无促销(NONE)、双11(Double11)、黑色星期五(BlackFriday)。
实现方案:
java复制public class DiscountCalculator {
public double calculateDiscount(String memberLevel, String promotion) {
// 参数验证
if (memberLevel == null || promotion == null) {
throw new IllegalArgumentException("参数不能为null");
}
double discount = 1.0; // 默认无折扣
// 先根据促销活动确定基础折扣
switch (promotion) {
case "Double11":
discount = 0.8; // 双11基础8折
break;
case "BlackFriday":
discount = 0.7; // 黑五基础7折
break;
case "NONE":
// 保持默认折扣
break;
default:
throw new IllegalArgumentException("未知促销活动: " + promotion);
}
// 根据会员等级叠加折扣
if ("SILVER".equals(memberLevel)) {
discount *= 0.95; // 银牌会员额外95折
} else if ("GOLD".equals(memberLevel)) {
discount *= 0.9; // 金牌会员额外9折
} else if ("DIAMOND".equals(memberLevel)) {
discount *= 0.85; // 钻石会员额外85折
} else if (!"NORMAL".equals(memberLevel)) {
throw new IllegalArgumentException("未知会员等级: " + memberLevel);
}
// 确保折扣不低于5折
if (discount < 0.5) {
discount = 0.5;
}
return discount;
}
}
代码分析:
- 使用switch处理离散的促销活动类型
- 使用if-else if处理会员等级折扣叠加
- 包含参数验证和边界条件处理
- 最后确保折扣不低于设定的下限
6.3 案例三:游戏状态机处理
需求描述:实现一个简单游戏的状态转换逻辑。游戏状态有:开始(START)、运行中(RUNNING)、暂停(PAUSED)、结束(END)。根据用户输入的命令(开始、暂停、继续、结束)来转换状态。
实现方案:
java复制public class GameStateMachine {
private String currentState = "START";
public void processCommand(String command) {
if (command == null) {
throw new IllegalArgumentException("命令不能为null");
}
switch (currentState) {
case "START":
if ("start".equals(command)) {
currentState = "RUNNING";
System.out.println("游戏开始运行");
} else {
System.out.println("无效命令: 游戏尚未开始");
}
break;
case "RUNNING":
if ("pause".equals(command)) {
currentState = "PAUSED";
System.out.println("游戏已暂停");
} else if ("end".equals(command)) {
currentState = "END";
System.out.println("游戏结束");
} else {
System.out.println("无效命令: 游戏正在运行中");
}
break;
case "PAUSED":
if ("resume".equals(command)) {
currentState = "RUNNING";
System.out.println("游戏继续运行");
} else if ("end".equals(command)) {
currentState = "END";
System.out.println("游戏结束");
} else {
System.out.println("无效命令: 游戏已暂停");
}
break;
case "END":
System.out.println("游戏已结束,请重新开始");
break;
default:
throw new IllegalStateException("未知游戏状态: " + currentState);
}
}
}
代码分析:
- 使用switch处理当前状态
- 在每个状态case内部使用if处理可能的命令
- 包含状态验证和错误处理
- 清晰地表达了状态转换逻辑
6.4 案例四:银行交易验证系统
需求描述:验证银行交易是否允许执行。需要考虑:账户状态、交易类型(存款、取款、转账)、余额是否充足、交易限额等因素。
实现方案:
java复制public class TransactionValidator {
public boolean validateTransaction(Account account, Transaction transaction) {
// 基本验证
if (account == null || transaction == null) {
throw new IllegalArgumentException("账户和交易不能为null");
}
if (!account.isActive()) {
System.out.println("交易失败:账户未激活");
return false;
}
if (account.isFrozen()) {
System.out.println("交易失败:账户已冻结");
return false;
}
// 根据交易类型进行验证
switch (transaction.getType()) {
case "DEPOSIT":
// 存款验证较简单
if (transaction.getAmount() <= 0) {
System.out.println("交易失败:存款金额必须为正数");
return false;
}
break;
case "WITHDRAWAL":
// 取款验证
if (transaction.getAmount() <= 0) {
System.out.println("交易失败:取款金额必须为正数");
return false;
}
if (transaction.getAmount() > account.getBalance()) {
System.out.println("交易失败:余额不足");
return false;
}
if (transaction.getAmount() > account.getDailyWithdrawalLimit()) {
System.out.println("交易失败:超过单日取款限额");
return false;
}
break;
case "TRANSFER":
// 转账验证
if (transaction.getAmount() <= 0) {
System.out.println("交易失败:转账金额必须为正数");
return false;
}
if (transaction.getAmount() > account.getBalance()) {
System.out.println("交易失败:余额不足");
return false;
}
if (transaction.getTargetAccount() == null) {
System.out.println("交易失败:缺少目标账户");
return false;
}
if (!transaction.getTargetAccount().isActive()) {
System.out.println("交易失败:目标账户未激活");
return false;
}
break;
default:
System.out.println("交易失败:未知交易类型");
return false;
}
// 所有验证通过
System.out.println("交易验证通过");
return true;
}
}
代码分析:
- 使用if进行前置条件检查和基本验证
- 使用switch处理不同类型的交易
- 每种交易类型有特定的验证规则
- 清晰的失败原因输出
- 严格的参数验证和错误处理
6.5 案例五:天气预报提示系统
需求描述:根据天气状况(晴天、雨天、雪天、雾天等)和温度,给出相应的活动建议和注意事项。
实现方案:
java复制public class WeatherAdvisor {
public void provideAdvice(String weatherCondition, int temperature) {
// 参数验证
if (weatherCondition == null) {
throw new IllegalArgumentException("天气状况不能为null");
}
// 根据天气状况给出建议
switch (weatherCondition) {
case "SUNNY":
if (temperature > 30) {
System.out.println("天气炎热,建议:");
System.out.println("- 避免正午外出");
System.out.println("- 使用防晒霜");
System.out.println("- 多喝水");
} else {
System.out.println("天气晴朗,适合:");
System.out.println("- 户外活动");
System.out.println("- 晾晒衣物");
}
break;
case "RAINY":
System.out.println("下雨天,建议:");
System.out.println("- 携带雨具");
if (temperature < 10) {
System.out.println("- 注意保暖,小心感冒");
}
break;
case "SNOWY":
System.out.println("下雪天,建议:");
System.out.println("- 穿戴保暖衣物");
System.out.println("- 注意路面结冰");
if (temperature < -5) {
System.out.println("- 极端寒冷,尽量减少外出");
}
break;
case "FOGGY":
System.out.println("雾天,建议:");
System.out.println("- 驾驶时开启雾灯");
System.out.println("- 减速慢行");
break;
default:
System.out.println("未知天气状况,请自行判断");
}
// 通用的温度建议
if (temperature < 0) {
System.out.println("温馨提示:气温低于零度,注意防寒保暖");
} else if (temperature > 35) {
System.out.println("温馨提示:高温天气,谨防中暑");
}
}
}
代码分析:
- 使用switch处理主要天气状况
- 在每个天气case内部使用if处理温度相关的建议
- 最后提供通用的温度建议
- 输出格式清晰,建议具体实用
7. 高级主题与最佳实践
掌握了分支结构的基础用法后,让我们深入探讨一些高级主题和行业内的最佳实践。这些知识将帮助你编写更专业、更健壮的Java代码,并避免常见的陷阱和反模式。
7.1 使用枚举增强switch语句
枚举类型与switch语句是天作之合。使用枚举可以避免字符串或整型常量带来的"魔数"问题,提高代码的可读性和类型安全性。
示例:
java复制public enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY,
FRIDAY, SATURDAY, SUNDAY
}
public class EnumSwitchExample {
public void printDayType(Day day) {
switch (day) {
case MONDAY:
System.out.println("一周的开始");
break;
case FRIDAY:
System.out.println("周末快到了");
break;
case SATURDAY: case SUNDAY:
System.out.println("周末愉快");
break;
default:
System.out.println("普通工作日");
}
}
}
优点:
- 编译时类型检查
- 自动补全支持
- 可读性强
- 避免拼写错误
7.2 使用多态替代复杂分支
当分支逻辑基于对象类型时,考虑使用多态来替代条件语句。这是面向对象设计中"替换条件语句多态"的重构手法。
重构前:
java复制public class AnimalHandler {
public void handle(Animal animal) {
if (animal instanceof Dog) {
((Dog) animal).bark();
} else if (animal instanceof Cat) {
((Cat) animal).meow();
} else if (animal instanceof Bird) {
((Bird) animal).sing();
}
}
}
重构后:
java复制public abstract class Animal {
public abstract void makeSound();
}
public class Dog extends Animal {
@Override
public void makeSound() {
bark();
}
private void bark() {
System.out.println("汪汪");
}
}
public class AnimalHandler {
public void handle(Animal animal) {
animal.makeSound();
}
}
优点:
- 符合开闭原则(对扩展开放,对修改封闭)
- 消除类型检查和强制转换
- 更易于维护和扩展
7.3 使用策略模式封装分支逻辑
当分支代表不同的算法或策略时,可以使用策略模式将每个分支的逻辑封装到单独的类中。
示例:
java复制interface DiscountStrategy {
double applyDiscount(double originalPrice);
}
class RegularCustomerDiscount implements DiscountStrategy {
@Override
public double applyDiscount(double originalPrice) {
return originalPrice * 0.95;
}
}
class VIPCustomerDiscount implements DiscountStrategy {
@Override
public double applyDiscount(double originalPrice) {
return originalPrice * 0.8;
}
}
public class DiscountContext {
private DiscountStrategy strategy;
public void setStrategy(DiscountStrategy strategy) {
this.strategy = strategy;
}
public double executeStrategy(double originalPrice) {
return strategy.applyDiscount(originalPrice);
}
}
优点:
- 可以在运行时切换策略
- 避免庞大的条件语句
- 每种策略可以独立测试
7.4 使用工厂模式创建对象
当分支用于创建不同类型的对象时,可以使用工厂模式来封装创建逻辑。
示例:
java复制public interface Logger {
void log(String message);
}
public class FileLogger implements Logger {
@Override
public void log(String message) {
// 写入文件
}
}
public class ConsoleLogger implements Logger {
@Override
public void log(String message) {
System.out.println(message);
}
}
public class LoggerFactory {
public static Logger createLogger(String type) {
switch (type) {
case "file":
return new FileLogger();
case "console":
return new ConsoleLogger();
default:
throw new IllegalArgumentException("未知的日志类型: " + type);
}
}
}
优点:
- 集中管理对象创建逻辑
- 客户端代码与具体实现解耦
- 易于扩展新的产品类型
7.5 使用Map替代switch语句
在某些情况下,可以使用Map来存储和查找处理逻辑,从而替代switch语句。
示例:
java复制public class CommandProcessor {
private Map<String, Runnable> commandMap = new HashMap<>();
public CommandProcessor() {
commandMap.put("start", this::processStart);
commandMap.put("stop", this::processStop);
commandMap.put("restart", this::processRestart);
}
public void processCommand(String command) {
Runnable handler = commandMap.get(command);
if (handler != null) {
handler.run();
} else {
System.out.println("未知命令: " + command);
}
}
private void processStart() {
System.out.println("处理启动命令");
}
private void processStop() {
System.out.println("处理停止命令");
}
private void processRestart() {
System.out.println("处理重启命令");
}
}
优点:
- 更易于动态添加或修改处理逻辑
- 代码结构更灵活
- 适用于命令模式等场景
7.6 使用Java 12+的switch表达式
从Java 12开始,switch表达式有了更简洁的语法,可以使用箭头符号(->)和yield关键字。
传统switch语句:
java复制int days = 0;
switch (month) {
case 1: case 3: case 5: case 7: case 8: case 10: case 12:
days = 31;
break;
case 4: case 6: case 9: case 11:
days = 30;
break;
case 2:
days = isLeapYear ? 29 : 28;
break;
default:
throw new IllegalArgumentException("无效月份");
}
Java 12+ switch表达式:
java复制int days = switch (month) {
case 1, 3, 5, 7, 8, 10, 12 -> 31;
case 4, 6, 9, 11 -> 30;
case 2 -> isLeapYear ? 29 : 28;
default -> throw new IllegalArgumentException("无效月份");
};
优点:
- 语法更简洁
- 可以直接返回值
- 减少了break的使用
- 更易于阅读和维护
7.7 分支结构的测试策略
为确保分支结构的正确性,需要制定有效的测试策略:
- 全覆盖测试:确保测试用例覆盖所有分支
- 边界值测试:特别测试条件边界情况
- null测试:验证对null输入的处理
- 异常测试:验证异常条件的处理
- 组合测试:当有多个条件组合时,测试各种组合情况
JUnit测试示例:
java复制public class DiscountCalculatorTest {
private DiscountCalculator calculator = new DiscountCalculator();
@Test
public void testNormalMemberNoPromotion() {
assertEquals(1.0, calculator.calculateDiscount("NORMAL", "NONE"), 0.001);
}
@Test
public void testGoldMemberDouble11() {
assertEquals(0.72, calculator.calculateDiscount("GOLD", "Double11"), 0.001);
}
@Test(expected = IllegalArgumentException.class)
public void testInvalidMemberLevel() {
calculator.calculateDiscount("INVALID", "NONE");
}