本文主要是记录一下用lambda 表达式优化代码的经历,篇幅不长,算是分享我觉得不错的一个小技巧。
话不多说,直接进入正题。
我们先来看这么一段代码:
@Component public class ConfigCacheHelper { private final RedisHelper redisHelper; private final IChannelConfigMapper iChannelConfigMapper; @Autowired public ConfigCacheHelper(RedisHelper redisHelper, IChannelConfigMapper iChannelConfigMapper) { this.redisHelper = redisHelper; this.iChannelConfigMapper = iChannelConfigMapper; } public AaaChannelConfig getAaaChannelConfig(String merchantId){ if (StringUtils.isEmpty(merchantId)){ throw new IllegalArgumentException("商户号不能为空"); } Object obj = redisHelper.hget(RedisKey.CHANEL_CONFIG, RedisKey.AAA_CHANNEL); AaaChannelConfig config; if (obj == null){ config = iChannelConfigMapper.selectAaaChannelConfig(merchantId); } else { Map<String, AaaChannelConfig> map = (Map<String, AaaChannelConfig>)obj; config = map.get(merchantId); } return Objects.requireNonNull(config, "获取Aaa渠道配置为空"); } public BbbChannelConfig getBbbChannelConfig(String merchantId){ if (StringUtils.isEmpty(merchantId)){ throw new IllegalArgumentException("商户号不能为空"); } Object obj = redisHelper.hget(RedisKey.CHANEL_CONFIG, RedisKey.BBB_CHANNEL); BbbChannelConfig config; if (obj == null){ config = iChannelConfigMapper.selectBbbChannelConfig(merchantId); } else { Map<String, BbbChannelConfig> map = (Map<String, BbbChannelConfig>)obj; config = map.get(merchantId); } return Objects.requireNonNull(config, "获取Bbb渠道配置为空"); } public CccChannelConfig getCccChannelConfig(String merchantId, String posId, String operatorId){ if (StringUtils.isEmpty(merchantId)){ throw new IllegalArgumentException("商户号不能为空"); } Object obj = redisHelper.hget(RedisKey.CHANEL_CONFIG, RedisKey.CCC_CHANNEL); CccChannelConfig config; if (obj == null){ config = iChannelConfigMapper.selectCccChannelConfig(merchantId, posId, operatorId); } else { Map<String, CccChannelConfig> map = (Map<String, CccChannelConfig>)obj; config = map.get(String.format("%s_%s_%s", merchantId, posId, operatorId)); } return Objects.requireNonNull(config, "获取Ccc渠道配置为空"); } // ... 此处再省略N个渠道的config }
俺是做支付的,这段代码的逻辑很简单,就是获取某个支付渠道的商户配置,缓存取不到就去数据库取。
在IDEA乍一看,我倒没看出什么问题,代码检查插件也没有报什么warning,但是当我在这个类里面新增获取第N个渠道的方法的时候,我就感觉到了这块代码不是很优雅。
总结出来两点:
多余的StringUtils.isEmpty(merchantId)
if (StringUtils.isEmpty(merchantId)){ throw new IllegalArgumentException("商户号不能为空"); }
理由有二:
getXXXChannelConfig
逻辑可以提取成如下
public AaaChannelConfig getAaaChannelConfig(String merchantId){ // if (StringUtils.isEmpty(merchantId)){ // throw new IllegalArgumentException("商户号不能为空"); // } // 1️⃣ Object obj = redisHelper.hget(RedisKey.CHANEL_CONFIG, {渠道键值}); AaaChannelConfig config; if (obj == null){ // 2️⃣ // selectBbbChannelConfig() // selectCccChannelConfig(merchantId, posId, operatorId) config = iChannelConfigMapper.{取某个渠道配置的方法}(...); } else { Map<String, AaaChannelConfig> map = (Map<String, AaaChannelConfig>)obj; // 3️⃣ // config = map.get(String.format("%s_%s_%s", merchantId, posId, operatorId)); // config = map.get(merchantId); config = map.get({渠道配置Map的键值}); } return Objects.requireNonNull(config, "获取Aaa渠道配置为空"); }
第一点很简单,不讲了。主要来讲下第二点,从上面的分析中,就可以抽取出3个变量:
这样子我们就可以把代码改造成下面这样:
private <T> T getChannelConfig(String configKey, String configMapInnerKey, Supplier<T> daoSupplier) { Object obj = redisHelper.hget(RedisKey.CHANNELPROXY_HKEY, configKey); T config; if (obj == null){ config = daoSupplier.get(); } else { Map<String, T> map = (Map<String, T>)obj; config = map.get(configMapInnerKey); } return Objects.requireNonNull(config, "获取渠道配置为空, 渠道值:" + configKey); } public AaaChannelConfig getAaaChannelConfig(String merchantId){ return getChannelConfig( RedisKey.AAA_CHANNEL, merchantId, () -> iChannelConfigMapper.selectAaaChannelConfig(merchantId) ); } public BbbChannelConfig getBbbChannelConfig(String merchantId){ return getChannelConfig( RedisKey.BBB_CHANNEL, merchantId, () -> iChannelConfigMapper.selectBbbChannelConfig(merchantId) ); } public CccChannelConfig getCccChannelConfig(String merchantId, String posId, String operatorId){ return getChannelConfig( RedisKey.CCC_CHANNEL, String.format("%s_%s_%s", merchantId, posId, operatorId), () -> iChannelConfigMapper.selectCccChannelConfig(merchantId, posId, operatorId) ); }
这里简单提一下Supplier
,这是java.util.function
中提供的函数式接口,用来支持Java 中的函数式编程。从语义上理解就是“T的提供者”,比如在上文语境中就是对应渠道配置的提供者。类似的常用接口还有:
接口 | 参数 | 返回类型 |
---|---|---|
Predicate<T> |
T | boolean |
Consumer<T> |
T | void |
Function<T,R> |
T | R |
Supplier<T> |
None | T |
UnaryOperator<T> |
T | T |
这样优化之后,提升了代码的可读性,在实现相同功能的前提下,比原来减少了一半的代码量。(233 -> 137)
这里想要再提一个场景,因为之前有碰到过,就是如何让你的Function抛出异常?
还是拿上述代码为例,
private <T> T getChannelConfig(String configKey, String configMapInnerKey, Supplier<T> daoSupplier) { Object obj = redisHelper.hget(RedisKey.CHANNELPROXY_HKEY, configKey); T config; if (obj == null){ // 假设我想让这个方法抛出一个自定义的BizException 业务异常,怎么办? config = daoSupplier.get(); } else { Map<String, T> map = (Map<String, T>)obj; config = map.get(configMapInnerKey); } return Objects.requireNonNull(config, "获取渠道配置为空, 渠道值:" + configKey); }
我刚开始想了蛮久的,后面发现这实际上是属于Java 基础方面的知识。
我们传入的参数daoSuppier
实际上相当于是一个函数式接口Supplier
的匿名内部实现类而已(当然底层实现是不一样的,在某种意义上比匿名内部类好很多,无论是性能,可读性还是使用趋势)
@FunctionalInterface public interface Supplier<T> { /** * Gets a result. * * @return a result */ T get(); }
接口可以声明抛出某个异常,它的实现可以不抛出异常,反之呢,如果它的实现抛出了受检异常,这个接口就必须显式声明抛出这个异常。
所以这种情况下,我们如果想我们的Supplier
抛出我们想要的异常,那么就需要自己声明一个Functional Interface
,
public interface DaoSupplier<T> { /** * Gets a result. * * @return a result */ T get() throws BizException; }
再次改造后的代码:
private <T> T getChannelConfig(String configKey, String configMapInnerKey, DaoSupplier<T> daoSupplier) throws BizException { Object obj = redisHelper.hget(RedisKey.CHANNELPROXY_HKEY, configKey); T config; if (obj == null){ config = daoSupplier.get(); } else { Map<String, T> map = (Map<String, T>)obj; config = map.get(configMapInnerKey); } return Objects.requireNonNull(config, "获取渠道配置为空, 渠道值:" + configKey); } public AaaChannelConfig getAaaPayChannelConfig(String merchantId) throws BizException { return getChannelConfig( RedisKey.AAA_CHANNEL, merchantId, () -> Optional.ofNullable(iChannelConfigMapper.selectAliPayChannelConfig(appId)) .orElseThrow(BizException::new) ); }
本文并没有引入很多的Java Lambda的原理性介绍、API介绍,因为本身就是个人的开发小记,偏重于实践,引入太多的知识性介绍反而偏离了本意。希望Java lambda 不熟悉的同学可以自己学习下相关资料。
如果本文有帮助到你,希望能点个赞,这是对我的最大动力🤝🤝🤗🤗。