代码中,如果 if-else 语句比较多,阅读起来比较困难,维护性较差,很容易出bug。接下来,此文将介绍优化 if-else 代码的七种方案:
如果 if-else 代码块包含 return 语句,可以考虑通过提前 return,把多余 else 干掉,使代码更加优雅
优化前:
if (condition) {
// TODO
} else {
return;
}
优化后:
if (!condition) {
return;
}
// TODO
使用条件三目运算符可以简化某些 if-else,使代码更加简洁,更具有可读性
优化前:
int status = 0;
if (condition) {
status = 1;
} else {
status = 2;
}
优化后:
int satus = condition ? 1 : 2
在某些时候,使用枚举也可以优化 if-else 逻辑分支(也可以看做一种表驱动方法)
优化前:
String orderStatusDes = "";
if (orderStatus == 0) {
orderStatusDes = "订单未支付";
} else if (orderStatus == 1) {
orderStatusDes = "订单已支付";
} else if (orderStatus == 2) {
orderStatusDes = "已发货";
}
优化后:
定义一个枚举类:
public enum OrderStatusEnum {
NOT_EXIST(-1, "不存在")
,
NOT_PAID(0, "订单未支付")
,
PAID(1, "订单已支付")
,
SENDED(2, "已发货");
private Integer code;
private String message;
OrderStatusEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
// 枚举列类中定义一个自定义方法,但如果要想能够被外面访问,需要定义成 static 类型
public static OrderStatusEnum getOrderStatusEnum(Integer status) {
for (OrderStatusEnum statusEnum : OrderStatusEnum.values()) {
if (status.equals(statusEnum.getMessage())) {
return statusEnum;
}
}
return NOT_EXIST;
}
}
有了枚举之后,以上 if-else 逻辑分支,可以优化为:
String orderStatusDes = OrderStatusEnum.getOrderStatusEnum(orderStatus).getMessage();
如果一个工程中,定义的枚举类较多,可以实现一个共同的接口
如果有一系列条件返回一样的结果,可以将它们合并为一个条件表达式,让逻辑更加清晰
优化前:
if (age < 18) {
return 100;
}
if ("zzc".equals(name)) {
return 100;
}
// TODO
优化后:
if (age < 18 || "zzc".equals(name)) {
return 100;
}
// TODO
将条件反转使异常情况先退出,让正常流程维持在主干流程,可以让代码结构更加清晰
优化前:
if (captial <= 0.0) {
return 0.0;
}
if (rate > 0 && duration > 0) {
return (income / duration) * rate;
}
return 0.0;
优化后:
if (captial <= 0.0) {
return 0.0;
}
if (rate <= 0 || duration <= 0) {
return 0.0;
}
return (income / duration) * rate;
有时候 if-else 比较多,是因为非空判断导致的,这时候你可以使用 java8 的 Optional 类进行优化
优化前:
if (user != null) {
if (user.getCity() != null) {
if (user.getCity().getCode() != null) {
String value = user.getCity().getCode().getValue();
}
}
}
优化后:
Optional.ofNullable(user).map(User::getCity).map(City::getCode).map(Code::getValue).orElse("init");
根据字段名(年龄、姓名、出生地)不同,将对象集合进行排序(可以升序、降序)
优化前:
// 属性值
String props = "name";
// 升序、降序
String order = "ascending";
List<User> users = new ArrayList<>(); // 将 users 进行赋值
if ("age".equals(props )) {
if ("ascending".equals(order)) {
// 升序排序
} else {
// 降序排序
}
} else if ("name".equals(props)) {
if ("ascending".equals(order)) {
// 升序排序
} else {
// 降序排序
}
} else if ("birthPlace".equals(props)) {
if ("ascending".equals(order)) {
// 升序排序
} else {
// 降序排序
}
}
优化后:
把每个条件逻辑代码块,抽象成一个公共的接口:
public interface PropService {
// 升序
void sortAscend(List<User> users);
// 降序
void sortDescend(List<User> users);
}
根据每个逻辑条件,定义相对应的策略实现:
public class NamePropServiceImpl implements PropService {
@Override
public void sortAscend(List<User> users) {
Collections.sort(users, Comparator.comparing(User::getName));
}
@Override
public void sortDescend(List<User> users) {
Collections.sort(users, Comparator.comparing(User::getName).reversed());
}
}
public class AgePropServiceImpl implements PropService {
@Override
public void sortAscend(List<User> users) {
Collections.sort(users, Comparator.comparing(User::getAge));
}
@Override
public void sortDescend(List<User> users) {
Collections.sort(users, Comparator.comparing(User::getAge).reversed());
}
}
public class BirthPlacePropServiceImpl implements PropService {
@Override
public void sortAscend(List<User> users) {
Collections.sort(users, Comparator.comparing(User::getBirthPlace));
}
@Override
public void sortDescend(List<User> users) {
Collections.sort(users, Comparator.comparing(User::getBirthPlace).reversed());
}
}
再定义策略工厂类,用来管理这些属性实现策略类,如下:
public class PropServiceFactory {
private static final Map<String, PropService> propServiceMap = new ConcurrentHashMap<>();
static {
propServiceMap.put("name", new NamePropServiceImpl());
propServiceMap.put("age", new AgePropServiceImpl());
propServiceMap.put("birthPlace", new BirthPlacePropServiceImpl());
}
public static PropService getPropService(String props) {
return propServiceMap.get(props);
}
}
使用了策略 + 工厂模式之后,代码变得简洁多了,如下:
public class Test {
public static void main(String[] args) {
String props = "name";
List<User> user = new ArrayList<>();
PropService propService = PropServiceFactory.getPropService(props);
// 升序
propService.sortAscend(user);
// 降序
propService.sortDescend(user);
}
}
在工厂类中,我们需要自己手动将属性策略类添加到一个 Map 中去,那么,当我们需要添加一个新的属性名排序时,就需要修改工厂类的源代码了,所以,这里可以进一步改造!!
在 Spring 初始化时,需要将策略模式类都注册到工厂类中,将策略类进行改造,使用 Spring 的 InitializingBean 接口,在 bean 初始化后,执行 afterPropertiesSet() 方法进行注册。代码如下:
public class NamePropServiceImpl implements PropService, InitializingBean {
@Override
public void sortAscend(List<User> users) {
Collections.sort(users, Comparator.comparing(User::getName));
}
@Override
public void sortDescend(List<User> users) {
Collections.sort(users, Comparator.comparing(User::getName).reversed());
}
@Override
public void afterPropertiesSet(){
PropServiceFactory.register("name", this);
}
}
其它与之相似,这里就省略了。
工厂类稍作改变:
public class PropServiceFactory {
private static final Map<String, PropService> propServiceMap = new ConcurrentHashMap<>();
public static void register(String props, PropService propService){
Assert.notNull(props, "Props can't be null");
propServiceMap.put(props, PropService);
}
public static PropService getPropService(String props) {
return propServiceMap.get(props);
}
}
属性策略实现类都不变
添加一个枚举:
public enum PropServiceEnum {
NAME_PROP("com.zzc.service.NamePropServiceImpl", "名称")
,
AGE_PROP("com.zzc.service.AgePropServiceImpl", "年龄")
,
BIRTH_PLACE_PROP("com.zzc.service.BirthPlacePropServiceImpl", "出生地")
;
// 类的全限定名
private String propValue;
private String propName;
PropServiceEnum(String propValue, String propName) {
this.propValue = propValue;
this.propName = propName;
}
public String getPropValue() {
return propValue;
}
public String getPropName() {
return propName;
}
}
修改工厂类:
public class PropServiceFactory {
public static PropService getPropService(String props) {
String className = PropServiceEnum.valueOf(props).getClassName();
return (PropService) Class.forName(className).newInstance();
}
}
===========================================================================================
2021-12-09 更:
优化前:
if ("文本" == msgType) {
// TODO
} else if ("图片" == msgType) {
// TODO
} else if ("视频" == msgType) {
// TODO
} else {
// TODO
}
根据消息的不同类型有不同的处理策略,如果都放在这种 if else 代码块中,代码很难维护,也很丑。所以,这里就用了策略模式来处理这种情况。
策略模式:就是定义一个接口,然后有多个实现类,每种实现类封装了一种行为。然后根据条件的不同选择不同的实现类。
优化后:
定义一个消息对象:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MsgVo {
private Integer type;
private String content;
}
定义一个消息类型的枚举类:
public enum MsgTypeEnum {
TEXT(1, "文本")
,
IMAGE(2, "图片")
,
VIDEO(3, "视频")
;
private Integer type;
private String msg;
MsgTypeEnum(Integer type, String msg) {
this.type = type;
this.msg = msg;
}
}
定义一个消息处理接口:
public interface MsgService {
void handler(MsgVo msgVo);
}
处理文本消息实现类:
@Service
public class TextMsgServiceImpl implements MsgService {
@Override
public void handler(MsgVo msgVo) {
System.out.println("处理文本消息:" + msgVo.getContent());
}
}
处理图片消息实现类:
@Service
public class ImageMsgServiceImpl implements MsgService {
@Override
public void handler(MsgVo msgVo) {
System.out.println("处理图片消息:" + msgVo.getContent());
}
}
我们也可以使用一个 Map
来维护消息类型–消息处理对象关系。这样直接根据消息类型就能拿到消息处理对象,调用消息处理对象的方法即可。
但是我们不想手动维护这个 Map 对象,因为每次增加新的消息处理类,Map 的初始化过程就得修改。
这里使用了 自定义注解 + ApplicationListener 来保存这种映射关系:
自定义注解 MsgTypeHandler
:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MsgTypeHandler {
MsgTypeEnum value();
}
然后,给每一个类型添加上这个注解类型:
@Service
@MsgTypeHandler(value = MsgTypeEnum.IMAGE)
public class ImageMsgServiceImpl implements MsgService {
// ...
}
@Service
@MsgTypeHandler(value = MsgTypeEnum.TEXT)
public class TextMsgServiceImpl implements MsgService {
// ...
}
用一个 context 对象保存了消息类型->消息处理对象的映射关系:
@Component
public class MsgServiceContext {
private final Map<Integer, MsgService> msgServiceMap = new HashMap<>();
public MsgService getMsgService(Integer type) {
return msgServiceMap.get(type);
}
public void putMsgService(Integer type, MsgService msgService) {
msgServiceMap.put(type, msgService);
}
}
在 Spring 的启动过程中,通过解析注解,将消息类型->消息处理对象的映射关系保存到MsgServiceContext
对象中:
@Component
public class MsgServiceListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
Map<String, Object> beans = event.getApplicationContext().getBeansWithAnnotation(MsgTypeHandler.class);
MsgServiceContext msgServiceContext = event.getApplicationContext().getBean(MsgServiceContext.class);
beans.forEach((name, bean) -> {
MsgTypeHandler typeHandler = bean.getClass().getAnnotation(MsgTypeHandler.class);
msgServiceContext.putMsgService(typeHandler.value().getType(), (MsgService) bean);
});
}
}
在单元测试中进行测试:
@RunWith(SpringRunner.class)
@SpringBootTest
public class MsgServiceContextTest {
@Autowired
private MsgServiceContext msgServiceContext;
@Test
public void contextLoads() {
MsgVo msgVo = new MsgVo(MsgTypeEnum.TEXT.getType(), "消息内容");
MsgService msgService = msgServiceContext.getMsgService(msgVo.getType());
msgService.handler(msgVo);
}
}
===========================================================================================
2022-02-11 更:
基于 2.8 案例。
在 2.8 案例中,是基于 策略模式 进行优化的。这样的话,有一个明显的缺点:策略实现类会非常多!没法俯视整个分派的业务逻辑。
所以,这次的优化使用了 Map + 函数式 接口来减少类的产生。
Java 8 函数式接口:Java 8 中新增了许多函数式接口。这里,我使用的函数式接口是:Consumer
:接受一个输入参数并且无返回的操作
接下来看看如何使用它~~
1、先定义一个消息类型处理器 MsgTypeHandler
@Component
public class MsgTypeHandler {
@Autowired
private MsgTypeService msgTypeService;
// Consumer:要求只有一个入参,且为 String 类型
private Map<String, Consumer<String>> msgTypeMap = new HashMap<>();
@PostConstruct
public void dispatcherInit() {
msgTypeMap.put("文本", msgType -> msgTypeService.handlerTextMsg(msgType));
msgTypeMap.put("图片", msgType -> msgTypeService.handlerImageMsg(msgType));
}
public void handler(String msgType) {
Consumer<String> consumer = msgTypeMap.get(msgType);
if (null != consumer) {
consumer.accept(msgType);
return;
}
System.out.println("没有需要处理的消息类型~~");
}
}
上述代码用上了 Java 8 的新特性:lambda 表达式
这样子写的好处是非常直观,能直接看到判断条件对应的业务逻辑。
2、业务逻辑类 MsgTypeServiceImpl
@Service
public class MsgTypeServiceImpl implements MsgTypeService {
@Override
public void handlerTextMsg(String msgType) {
System.out.println("处理文本消息");
}
@Override
public void handlerImageMsg(String msgType) {
System.out.println("处理图片消息");
}
}
3、单元测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class MsgServiceContextTest {
@Autowired
private MsgTypeHandler msgTypeHandler;
@Test
public void contextLoads2() {
String msgType = "文本";
msgTypeHandler.handler(msgType);
}
}