Java短路选择器设计:pan common 告别if-else的三种模式
2026/6/4 15:36:25 网站建设 项目流程

Java短路选择器设计:告别if-else的三种模式

订单状态分发、事件路由、配置映射——这些场景下冗长的 if-else 链不仅难看,还会产生不必要的计算开销。本文分析pan-common中的短路选择器体系,如何用链式 API 替代 if-else,同时保持延迟计算和类型安全。

一、场景:状态码路由的三种写法

假设你要根据用户角色返回不同的权限码,传统写法是这样的:

Stringrole=getUserRole();intpermission;if("admin".equals(role)){permission=computeAdminPermission();// 可能很耗时的操作}elseif("user".equals(role)){permission=computeUserPermission();}elseif("guest".equals(role)){permission=computeGuestPermission();}else{permission=0;}

三个问题:

  1. 即使角色是"admin",其他分支的computeXxx方法也可能在编写时不小心被提前调用
  2. 分支多了之后,if-else 链难以阅读
  3. JDK 14 的 switch 表达式只能比较固定常量,无法处理复杂条件

pan-common(当前版本 2.0.6 / 3.0.6,Spring Boot 2.x / 3.x,JDK 17+)的ChooseEq/ChooseFirst体系就是为了解决这些问题。

二、环境准备

Maven 坐标:

<dependency><groupId>com.gitee.apanlh</groupId><artifactId>pan-common</artifactId><version>3.0.6</version><!-- Spring Boot 3.x;2.x 项目用 2.0.6 --></dependency>

无额外依赖,ChooseEq体系完全基于 JDK 内置类。

三、三种模式,覆盖不同场景

3.1 模式一:ChooseFirst— 任意布尔条件

最灵活的模式,支持任意boolean表达式作为条件。

// 替代 if-else 链Stringresult=ChooseFirst.create(role.equals("admin"),"管理员").when(role.equals("user"),"普通用户").when(role.equals("guest"),"访客").end("未知角色");

延迟计算版本(只有匹配到的分支才会执行):

// computeAdminValue() 只在 role 为 "admin" 时执行Integervalue=ChooseFirst.create(role.equals("admin"),()->computeAdminValue()).when(role.equals("user"),()->computeUserValue()).when(role.equals("guest"),()->computeGuestValue()).end(()->0);

空构造 + 后置分支:

// 先创建空选择器,后续动态添加条件ChooseFirst<Integer>selector=ChooseFirst.<Integer>create().when(status==200,2000).when(status==404,4000).when(status==500,5000);intcode=selector.end(9999);

3.2 模式二:FixedChooseEq— 固定源比较

当你有一个固定的变量需要和多个常量比较时,这是最简洁的模式。

// 固定源为 status,后续每个 when 只需传目标值Stringmsg=ChooseEq.create(status).when("SUCCESS","操作成功").when("FAIL","操作失败").when("NOT_FOUND","资源不存在").end("未知状态");

对比 switch 表达式:

// JDK 14+ switch 表达式Stringmsg=switch(status){case"SUCCESS"->"操作成功";case"FAIL"->"操作失败";case"NOT_FOUND"->"资源不存在";default->"未知状态";};

两者效果等价,但ChooseEq多了两个能力:

  • 延迟计算when("SUCCESS", () -> computeSuccessMsg())确保不匹配的分支不会执行
  • 动作模式:不需要返回值时,用endVoid()执行默认动作
// 仅执行动作,无返回值ChooseEq.create(eventType).when("USER_LOGIN",()->handleLogin()).when("USER_LOGOUT",()->handleLogout()).when("USER_REGISTER",()->handleRegister()).endVoid(()->log.warn("未知事件: {}",eventType));

3.3 模式三:ExplicitChooseEq— 两个变量都不固定

当比较的双方都是运行时变量时,使用显式比较模式。

// leftVal 和 rightVal 都是运行时变量Stringlabel=ChooseEq.create().when(leftVal,rightVal,"两者相等").when(leftVal,fallbackVal,"与备选值相等").end("都不匹配");

这在两个动态值的比较场景下很有用,比如协议版本协商:

// 客户端版本和服务端支持版本都是动态的Stringcompatibility=ChooseEq.create().when(clientVersion,serverVersion,"完全兼容").when(clientVersion,"1.0","向下兼容").when(serverVersion,"2.0","服务端已升级").end("需要版本检查");

四、核心设计解析

4.1 短路机制

所有选择器的核心逻辑只有一个判断:

// AbstractChooser.javaprotectedFuncCall<R>matchValueSupplier;// ChooseFirst.javaprotectedvoidaddCase(booleancondition,FuncCall<R>funcCall){if(this.matchValueSupplier==null&&condition){this.matchValueSupplier=funcCall;}}// FixedChooseEq.javaprivatevoidmatch(Ttarget,FuncCall<R>funcCall){if(this.matchValueSupplier!=null){return;// 已匹配,后续 when 全部忽略}if(Eq.autoEq(this.source,target)){this.matchValueSupplier=funcCall;}}

一旦matchValueSupplier被赋值(找到第一个匹配的分支),后续所有when调用直接返回,不会产生任何计算开销。

4.2 延迟计算

ChooseEqwhen方法接受两种参数:

方式示例求值时机
立即值.when("admin", 100)链式调用构建时就求值
延迟计算.when("admin", () -> computeValue())调用end()时,只有匹配才执行

关键区别在于:

// 立即值:所有分支的 computeXxx() 都会被调用,无论是否匹配Integerresult=ChooseEq.create(role).when("admin",computeAdminValue())// 立即执行.when("user",computeUserValue())// 立即执行.end(0);// 延迟计算:只有匹配的分支才会执行Integerresult=ChooseEq.create(role).when("admin",()->computeAdminValue())// 仅当 role="admin" 时执行.when("user",()->computeUserValue())// 仅当 role="user" 时执行.end(()->0);// 仅当无匹配时执行

4.3 类型推断桥接设计

Java 泛型在链式调用中存在类型推断困难。ChooseEq通过一个无状态的桥接器解决:

// 门面类publicstaticFixedSourceChooser<T>create(Tsource){returnnewFixedSourceChooser<>(source);// 无状态,只携带 source}// 桥接器:第一次 when 确定 <R> 泛型publicstaticfinalclassFixedSourceChooser<T>{privatefinalTsource;public<R>FixedChooseEq<T,R>when(Ttarget,Rvalue){// 此时 Java 编译器已经推断出 R 的类型returnFixedChooseEq.<T,R>create(this.source).when(target,value);}}

调用方不需要手动指定泛型参数:

// 不需要这样写ChooseEq.<String,Integer>create("admin").when("admin",100)// 编译器自动推断ChooseEq.create("admin").when("admin",100)// R = Integer 自动推断.end(0);

4.4Eq.autoEq智能比较

ChooseEq内部使用的不是简单的Objects.equals,而是Eq.autoEq,它支持:

  • null安全比较:null == null返回true
  • 数组比较:自动识别数组类型并进行深度比较
  • 枚举比较:按枚举实例或按名称
  • 对象比较:基于equals方法
publicstatic<T>booleanautoEq(Tvalue,TeqValue){if(value==null&&eqValue==null)returntrue;if(value==null||eqValue==null)returnfalse;// 自动识别数组类型进行深度比较if(value.getClass().isArray())returnautoEqArray(value,eqValue);returnEq.object(value,eqValue);}

4.5 7 种结束方式

AbstractChooser提供了丰富的结束方法,覆盖不同返回值需求:

// 1. end() — 无匹配返回 nullStringr1=ChooseEq.create(role).when("admin","管理员").end();// 2. end(defaultValue) — 无匹配返回固定默认值Stringr2=ChooseEq.create(role).when("admin","管理员").end("访客");// 3. end(FuncCall) — 无匹配时延迟计算默认值Stringr3=ChooseEq.create(role).when("admin","管理员").end(()->fetchDefaultRole());// 4. end(RuntimeException) — 无匹配时抛出异常Stringr4=ChooseEq.create(role).when("admin","管理员").end(newIllegalArgumentException("未知角色: "+role));// 5. endVoid(Runnable) — 无匹配时执行动作(无返回值)ChooseEq.create(event).when("LOGIN",()->handleLogin()).endVoid(()->log.warn("未知事件"));// 6. endOrDefault(defaultValue) — 匹配值为 null 时也返回默认值Stringr6=ChooseEq.create(role).when("admin",null).endOrDefault("默认值");// 7. endOrDefault(FuncCall) — 匹配值为 null 时延迟计算Stringr7=ChooseEq.create(role).when("admin",null).endOrDefault(()->"默认");

注意end(defaultValue)endOrDefault(defaultValue)的区别:

  • end(defaultValue):只要有匹配(即使匹配值是null),就返回匹配值,不返回默认值
  • endOrDefault(defaultValue):匹配值是null时也会返回默认值

这是一个容易踩坑的细节,根据你的业务语义选择。

五、与非空检查的对比

传统的非空值获取:

Stringvalue;if(cache.get(key)!=null){value=cache.get(key);}elseif(config.getDefault(key)!=null){value=config.getDefault(key);}elseif(fallback.get(key)!=null){value=fallback.get(key);}else{value="";}

ChooseFirst改写:

Stringvalue=ChooseFirst.create(cache.get(key)!=null,cache.get(key)).when(config.getDefault(key)!=null,config.getDefault(key)).when(fallback.get(key)!=null,fallback.get(key)).end("");

或者更推荐用延迟计算避免重复查询:

Stringvalue=ChooseFirst.<String>create().when(cache.get(key)!=null,()->cache.get(key)).when(config.getDefault(key)!=null,()->config.getDefault(key)).when(fallback.get(key)!=null,()->fallback.get(key)).end(()->"");

六、线程安全声明

ChooseEq/ChooseFirst及其构建器非线程安全,仅供单线程使用。每个链式调用创建新实例,不共享状态。如果在多线程场景中使用,请确保每个线程持有独立的实例。

这不是设计缺陷,而是刻意的取舍——短路选择器本身就是单次决策操作,多线程共享一个选择器没有语义意义。

七、局限性

ChooseEq不是万能的,以下场景不适用:

  • 范围判断(如age >= 18 && age < 65):ChooseFirst可以但可读性不如 if-else
  • 多条件组合(如A && B || C):短路选择器只支持单一布尔条件或相等比较
  • 性能敏感的热点路径:链式调用会产生少量对象创建开销,在纳秒级热点路径上不如 if-else

如果你的场景是 JDK 14+ 的简单 switch 表达式能解决的,直接用 switch 即可——原生语法不需要额外学习成本。ChooseEq的价值在于延迟计算、动作模式和动态条件这三个 switch 做不到的事情。

八、总结

pan-common的短路选择器体系用不到 400 行代码,提供了一套完整的 if-else 替代方案:

  1. 三种模式覆盖固定源比较、显式比较、任意条件判断
  2. 延迟计算确保不匹配的分支不会产生计算开销
  3. 类型推断桥接让调用方无需手动指定泛型参数
  4. 7 种结束方式覆盖返回值、异常、动作等多种需求

项目地址:https://gitee.com/apanlh/pan-common

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询