本文主要是記錄一下用lambda 表達式優化代碼的經歷,篇幅不長,算是分享我以爲不錯的一個小技巧。java
話很少說,直接進入正題。redis
咱們先來看這麼一段代碼:數據庫
@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個渠道的方法的時候,我就感受到了這塊代碼不是很優雅。緩存
總結出來兩點:app
多餘的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 不熟悉的同窗能夠本身學習下相關資料。
若是本文有幫助到你,但願能點個贊,這是對個人最大動力🤝🤝🤗🤗。