如何校验多个对象中仅有一个为非null值,其余均为null?
三个参数需恰好一个非空的优雅实现方案
场景要求三个对象中必须仅有一个为非null,另外两个必须为null。最直观的朴素实现方案如下:
if (param1 != null && param2 == null && param3 == null) { doSomething(param1); } else if (param1 == null && param2 != null && param3 == null) { doSomethingElse(param2); } else if (param1 == null && param2 == null && param3 != null) { doOtherThings(param3); }请问是否存在更优雅的实现方案,能够确保仅有一个对象为非null值?
以下是不同场景下可选用的优化实现:
方案1:抽离公共校验逻辑(固定参数场景首选,可读性最高)
把「判断一组参数中恰好有一个非空」的逻辑抽成通用工具方法,既避免重复写判断条件,又能提前拦截不合法输入:
// 通用工具方法,支持任意数量参数的恰好一个非空校验 private static boolean exactlyOneNotNull(Object... params) { int notNullCount = 0; for (Object param : params) { if (param != null) { notNullCount++; // 超过1个非空直接提前返回,无需遍历剩余参数 if (notNullCount > 1) { return false; } } } return notNullCount == 1; }
业务逻辑层代码可简化为:
// 先做合法性校验,不满足要求直接抛出异常终止流程 if (!exactlyOneNotNull(param1, param2, param3)) { throw new IllegalArgumentException("三个参数中必须恰好存在一个非空值"); } // 后续分支判断无需再校验另外两个参数是否为空 if (param1 != null) { doSomething(param1); } else if (param2 != null) { doSomethingElse(param2); } else { doOtherThings(param3); }
该方案改动最小,可读性强,新人也能快速看懂逻辑。
方案2:函数式绑定参数与处理逻辑(参数可能扩展场景首选,可完全消除if-else)
如果后续参数数量可能增加,或者想要彻底去掉分支判断,可以把参数和对应的处理逻辑绑定后遍历:
// 定义参数与对应处理逻辑的绑定关系,Java16以下版本可用AbstractMap.SimpleEntry或自定义POJO替代record record ParamHandler<T>(T param, Consumer<T> handler) {} // 遍历所有参数,找到非空的参数执行对应逻辑 List<ParamHandler<?>> validHandlers = Stream.of( new ParamHandler<>(param1, this::doSomething), new ParamHandler<>(param2, this::doSomethingElse), new ParamHandler<>(param3, this::doOtherThings) ) .filter(handler -> handler.param() != null) .collect(Collectors.toList()); if (validHandlers.size() != 1) { throw new IllegalArgumentException("三个参数中必须恰好存在一个非空值"); } validHandlers.get(0).handler().accept(validHandlers.get(0).param());
该方案扩展性极强,后续要加第四种参数场景,只需要在Stream构造的地方加一行新的ParamHandler即可,不需要修改原有逻辑。
方案3:类型层面约束(已引入函数式库的项目首选,安全性最高)
如果项目已经引入了Vavr这类工具库,可以直接用Either3类型来承载三种不同的参数,天然保证只能存在一种有效值,无需额外校验:
// 入参直接定义为Either3类型,从类型层面限制只能有一个有效值 Either3<Param1Type, Param2Type, Param3Type> param = ...; // 直接用match匹配对应逻辑,无需判空 param.match() .peekLeft(this::doSomething) .peekMiddle(this::doSomethingElse) .peekRight(this::doOtherThings);
内容的提问来源于stack exchange,提问作者Stanja
相关产品推荐
相关产品推荐

