Java8利用Function接口減少代碼重復(fù)的操作指南
前言
在 Java
開(kāi)發(fā)的征途中,我們時(shí)常與重復(fù)代碼不期而遇。這些重復(fù)代碼不僅讓項(xiàng)目顯得笨重,更增加了維護(hù)成本。幸運(yùn)的是,Java 8
帶來(lái)了函數(shù)式編程的春風(fēng),以 Function
接口為代表的一系列新特性,為我們提供了破除這一難題的利劍。本文將以一個(gè)實(shí)際應(yīng)用場(chǎng)景為例,即使用 Java 8
的函數(shù)式編程特性來(lái)重構(gòu)數(shù)據(jù)有效性斷言邏輯,展示如何通過(guò) SFunction
(基于 Java 8
的 Lambda
表達(dá)式封裝)減少代碼重復(fù),從而提升代碼的優(yōu)雅性和可維護(hù)性。
背景故事:數(shù)據(jù)校驗(yàn)的煩惱
想象一下,在一個(gè)復(fù)雜的業(yè)務(wù)系統(tǒng)中,我們可能需要頻繁地驗(yàn)證數(shù)據(jù)庫(kù)中某個(gè)字段值是否有效,是否符合預(yù)期值。傳統(tǒng)的做法可能充斥著大量相似的查詢邏輯,每次都需要手動(dòng)構(gòu)建查詢條件、執(zhí)行查詢并處理結(jié)果,這樣的代碼既冗長(zhǎng)又難以維護(hù)。
例如以下兩個(gè)驗(yàn)證用戶 ID 和部門(mén) ID 是否有效的方法,雖然簡(jiǎn)單,但每次需要校驗(yàn)不同實(shí)體或不同條件時(shí),就需要復(fù)制粘貼并做相應(yīng)修改,導(dǎo)致代碼庫(kù)中充滿了大量雷同的校驗(yàn)邏輯,給維護(hù)帶來(lái)了困擾。
java 體驗(yàn)AI代碼助手 代碼解讀復(fù)制代碼// 判斷用戶 ID 是否有效 public void checkUserExistence(String userId) { User user = userDao.findById(userId); if (user == null) { throw new RuntimeException("用戶ID無(wú)效"); } } // 判斷部門(mén) ID 是否有效 public void checkDeptExistence(String deptId) { Dept dept = deptDao.findById(deptId); if (dept == null) { throw new RuntimeException("部門(mén)ID無(wú)效"); } }
Java 8 的魔法棒:函數(shù)式接口
Java 8 引入了函數(shù)式接口的概念,其中 Function<T, R>
是最基礎(chǔ)的代表,它接受一個(gè)類型 T
的輸入,返回類型 R
的結(jié)果。而在 MyBatis Plus
等框架中常用的 SFunction
是對(duì) Lambda
表達(dá)式的進(jìn)一步封裝,使得我們可以更加靈活地操作實(shí)體類的屬性。
實(shí)戰(zhàn)演練:重構(gòu)斷言方法
下面的 ensureColumnValueValid
方法正是利用了函數(shù)式接口的魅力,實(shí)現(xiàn)了對(duì)任意實(shí)體類指定列值的有效性斷言:
java 體驗(yàn)AI代碼助手 代碼解讀復(fù)制代碼/** * 確認(rèn)數(shù)據(jù)庫(kù)字段值有效(通用) * * @param <V> 待驗(yàn)證值的類型 * @param valueToCheck 待驗(yàn)證的值 * @param columnExtractor 實(shí)體類屬性提取函數(shù) * @param queryExecutor 單條數(shù)據(jù)查詢執(zhí)行器 * @param errorMessage 異常提示信息模板 */ public static <T, R, V> void ensureColumnValueValid(V valueToCheck, SFunction<T, R> columnExtractor, SFunction<LambdaQueryWrapper<T>, T> queryExecutor, String errorMessage) { if (valueToCheck == null) return; LambdaQueryWrapper<T> wrapper = new LambdaQueryWrapper<>(); wrapper.select(columnExtractor); wrapper.eq(columnExtractor, valueToCheck); wrapper.last("LIMIT 1"); T entity = queryExecutor.apply(wrapper); R columnValue = columnExtractor.apply(entity); if (entity == null || columnValue == null) throw new DataValidationException(String.format(errorMessage, valueToCheck)); }
這個(gè)方法接受一個(gè)待驗(yàn)證的值、一個(gè)實(shí)體類屬性提取函數(shù)、一個(gè)單行數(shù)據(jù)查詢執(zhí)行器和一個(gè)異常信息模板作為參數(shù)。通過(guò)這四個(gè)參數(shù),不僅能夠進(jìn)行針對(duì)特定屬性的有效性檢查,而且還能生成具有一致性的異常信息。
對(duì)比分析
使用 Function 改造前
java 體驗(yàn)AI代碼助手 代碼解讀復(fù)制代碼// 判斷用戶 ID 是否有效 public void checkUserExistence(String userId) { User user = userDao.findById(userId); if (user == null) { throw new RuntimeException("用戶ID無(wú)效"); } } // 判斷部門(mén) ID 是否有效 public void checkDeptExistence(String deptId) { Dept dept = deptDao.findById(deptId); if (dept == null) { throw new RuntimeException("部門(mén)ID無(wú)效"); } }
使用 Function 改造后
java 體驗(yàn)AI代碼助手 代碼解讀復(fù)制代碼public void assignTaskToUser(AddOrderDTO dto) { ensureColumnValueValid(dto.getUserId(), User::getId, userDao::getOne, "用戶ID無(wú)效"); ensureColumnValueValid(dto.getDeptId(), Dept::getId, deptDao::getOne, "部門(mén)ID無(wú)效"); ensureColumnValueValid(dto.getCustomerId(), Customer::getId, customerDao::getOne, "客戶ID無(wú)效"); ensureColumnValueValid(dto.getDeptId(), Dept::getId, deptDao::getOne, "部門(mén)ID無(wú)效"); ensureColumnValueValid(dto.getSupplieId(), Supplie::getId, supplierDao::getOne, "供應(yīng)商ID無(wú)效"); // 現(xiàn)在可以確信客戶存在 Customer cus = customerDao.findById(dto.getCustomerId()); // 創(chuàng)建訂單的邏輯... }
對(duì)比上述兩段代碼,我們發(fā)現(xiàn)后者不僅大幅減少了代碼量,而且通過(guò)函數(shù)式編程,表達(dá)出更為清晰的邏輯意圖,可讀性和可維護(hù)性都有所提高。
優(yōu)點(diǎn)
- 減少重復(fù)代碼: 通過(guò)
ensureColumnValueValid
方法,所有涉及數(shù)據(jù)庫(kù)字段值有效性檢查的地方都可以復(fù)用相同的邏輯,將變化的部分作為參數(shù)傳遞,大大減少了因特定校驗(yàn)邏輯而產(chǎn)生的代碼量。 - 增強(qiáng)代碼復(fù)用: 抽象化的校驗(yàn)方法適用于多種場(chǎng)景,無(wú)論是用戶ID、訂單號(hào)還是其他任何實(shí)體屬性的校驗(yàn),一套邏輯即可應(yīng)對(duì)。
- 提升可讀性和維護(hù)性: 通過(guò)清晰的函數(shù)簽名和 Lambda 表達(dá)式,代碼意圖一目了然,降低了后續(xù)維護(hù)的成本。
- 靈活性和擴(kuò)展性: 當(dāng)校驗(yàn)規(guī)則發(fā)生變化時(shí),只需要調(diào)整
ensureColumnValueValid
方法或其內(nèi)部實(shí)現(xiàn),所有調(diào)用該方法的地方都會(huì)自動(dòng)受益,提高了系統(tǒng)的靈活性和擴(kuò)展性。
舉一反三:拓展校驗(yàn)邏輯的邊界
通過(guò)上述的實(shí)踐,我們見(jiàn)識(shí)到了函數(shù)式編程在簡(jiǎn)化數(shù)據(jù)校驗(yàn)邏輯方面的威力。但這只是冰山一角,我們可以根據(jù)不同的業(yè)務(wù)場(chǎng)景,繼續(xù)擴(kuò)展和完善校驗(yàn)邏輯,實(shí)現(xiàn)更多樣化的校驗(yàn)需求。以下兩個(gè)示例展示了如何在原有基礎(chǔ)上進(jìn)一步深化,實(shí)現(xiàn)更復(fù)雜的數(shù)據(jù)比較和驗(yàn)證功能。
斷言指定列值等于預(yù)期值
首先,考慮一個(gè)場(chǎng)景:除了驗(yàn)證數(shù)據(jù)的存在性,我們還需確認(rèn)查詢到的某列值是否與預(yù)期值相符。這在驗(yàn)證用戶角色、狀態(tài)變更等場(chǎng)景中尤為常見(jiàn)。為此,我們?cè)O(shè)計(jì)了 validateColumnValueMatchesExpected
方法:
java 體驗(yàn)AI代碼助手 代碼解讀復(fù)制代碼/** * 驗(yàn)證查詢結(jié)果中指定列的值是否與預(yù)期值匹配 * * @param <T> 實(shí)體類型 * @param <R> 目標(biāo)列值的類型 * @param <C> 查詢條件列值的類型 * @param targetColumn 目標(biāo)列的提取函數(shù),用于獲取想要驗(yàn)證的列值 * @param expectedValue 期望的列值 * @param conditionColumn 條件列的提取函數(shù),用于設(shè)置查詢條件 * @param conditionValue 條件列對(duì)應(yīng)的值 * @param queryMethod 執(zhí)行查詢的方法引用,返回單個(gè)實(shí)體對(duì)象 * @param errorMessage 驗(yàn)證失敗時(shí)拋出異常的錯(cuò)誤信息模板 * @throws RuntimeException 當(dāng)查詢結(jié)果中目標(biāo)列的值與預(yù)期值不匹配時(shí)拋出異常 */ public static <T, R, C> void validateColumnValueMatchesExpected( SFunction<T, R> targetColumn, R expectedValue, SFunction<T, C> conditionColumn, C conditionValue, SFunction<LambdaQueryWrapper<T>, T> queryMethod, String errorMessage) { // 創(chuàng)建查詢包裝器,選擇目標(biāo)列并設(shè)置查詢條件 LambdaQueryWrapper<T> wrapper = new LambdaQueryWrapper<>(); wrapper.select(targetColumn); wrapper.eq(conditionColumn, conditionValue); // 執(zhí)行查詢方法 T one = queryMethod.apply(wrapper); // 如果查詢結(jié)果為空,則直接返回,視為驗(yàn)證通過(guò)(或忽略) if (one == null) return; // 獲取查詢結(jié)果中目標(biāo)列的實(shí)際值 R actualValue = targetColumn.apply(one); // 比較實(shí)際值與預(yù)期值是否匹配,這里假設(shè)notMatch是一個(gè)自定義方法用于比較不匹配情況 boolean doesNotMatch = notMatch(actualValue, expectedValue); if (doesNotMatch) { // 若不匹配,則根據(jù)錯(cuò)誤信息模板拋出異常 throw new RuntimeException(String.format(errorMessage, expectedValue, actualValue)); } } // 假設(shè)的輔助方法,用于比較值是否不匹配,根據(jù)實(shí)際需要實(shí)現(xiàn) private static <R> boolean notMatch(R actual, R expected) { // 示例簡(jiǎn)單實(shí)現(xiàn)為不相等判斷,實(shí)際情況可能更復(fù)雜 return !Objects.equals(actual, expected); }
這個(gè)方法允許我們指定一個(gè)查詢目標(biāo)列(targetColumn)、預(yù)期值(expectedValue)、查詢條件列(conditionColumn)及其對(duì)應(yīng)的條件值(conditionValue),并提供一個(gè)查詢方法(queryMethod)來(lái)執(zhí)行查詢。如果查詢到的列值與預(yù)期不符,則拋出異常,錯(cuò)誤信息通過(guò) errorMessage 參數(shù)定制。
應(yīng)用場(chǎng)景: 例如在一個(gè)權(quán)限管理系統(tǒng)中,當(dāng)需要更新用戶角色時(shí),系統(tǒng)需要確保當(dāng)前用戶的角色在更新前是 “普通用戶”,才能將其升級(jí)為 “管理員”。此場(chǎng)景下,可以使用 validateColumnValueMatchesExpected
方法來(lái)驗(yàn)證用戶當(dāng)前的角色是否確實(shí)為“普通用戶”。
java 體驗(yàn)AI代碼助手 代碼解讀復(fù)制代碼// 當(dāng)用戶角色不是 “普通用戶” 時(shí)拋異常 validateColumnValueMatchesExpected(User::getRoleType, "普通用戶", User::getId, userId, userMapper::getOne, "用戶角色不是普通用戶,無(wú)法升級(jí)為管理員!");
斷言指定值位于期望值列表內(nèi)
進(jìn)一步,某些情況下我們需要驗(yàn)證查詢結(jié)果中的某一列值是否屬于一個(gè)預(yù)設(shè)的值集合。例如,驗(yàn)證用戶角色是否合法。為此,我們創(chuàng)建了 validateColumnValueMatchesExpectedList
方法:
java 體驗(yàn)AI代碼助手 代碼解讀復(fù)制代碼/** * 驗(yàn)證查詢結(jié)果中指定列的值是否位于預(yù)期值列表內(nèi) * * @param <T> 實(shí)體類型 * @param <R> 目標(biāo)列值的類型 * @param <C> 查詢條件列值的類型 * @param targetColumn 目標(biāo)列的提取函數(shù),用于獲取想要驗(yàn)證的列值 * @param expectedValueList 期望值的列表 * @param conditionColumn 條件列的提取函數(shù),用于設(shè)置查詢條件 * @param conditionValue 條件列對(duì)應(yīng)的值 * @param queryMethod 執(zhí)行查詢的方法引用,返回單個(gè)實(shí)體對(duì)象 * @param errorMessage 驗(yàn)證失敗時(shí)拋出異常的錯(cuò)誤信息模板 * @throws RuntimeException 當(dāng)查詢結(jié)果中目標(biāo)列的值不在預(yù)期值列表內(nèi)時(shí)拋出異常 */ public static <T, R, C> void validateColumnValueInExpectedList( SFunction<T, R> targetColumn, List<R> expectedValueList, SFunction<T, C> conditionColumn, C conditionValue, SFunction<LambdaQueryWrapper<T>, T> queryMethod, String errorMessage) { LambdaQueryWrapper<T> wrapper = new LambdaQueryWrapper<>(); wrapper.select(targetColumn); wrapper.eq(conditionColumn, conditionValue); T one = queryMethod.apply(wrapper); if (one == null) return; R actualValue = targetColumn.apply(one); if (actualValue == null) throw new RuntimeException("列查詢結(jié)果為空"); if (!expectedValueList.contains(actualValue)) { throw new RuntimeException(errorMessage); } }
這個(gè)方法接受一個(gè)目標(biāo)列(targetColumn)、一個(gè)預(yù)期值列表(expectedValueList)、查詢條件列(conditionColumn)及其條件值(conditionValue),同樣需要一個(gè)查詢方法(queryMethod)。如果查詢到的列值不在預(yù)期值列表中,則觸發(fā)異常。
應(yīng)用場(chǎng)景: 在一個(gè)電商平臺(tái)的訂單處理流程中,系統(tǒng)需要驗(yàn)證訂單狀態(tài)是否處于可取消的狀態(tài)列表里(如 “待支付”、“待發(fā)貨”)才允許用戶取消訂單。此時(shí),validateColumnValueInExpectedList
方法能有效確保操作的合法性。
// 假設(shè) OrderStatusEnum 枚舉了所有可能的訂單狀態(tài),cancelableStatuses 包含可取消的狀態(tài) List<String> cancelableStatuses = Arrays.asList(OrderStatusEnum.WAITING_PAYMENT.getValue(), OrderStatusEnum.WAITING_DELIVERY.getValue()); // 驗(yàn)證訂單狀態(tài)是否在可取消狀態(tài)列表內(nèi) validateColumnValueInExpectedList(Order::getStatus, cancelableStatuses, Order::getOrderId, orderId, orderMapper::selectOne, "訂單當(dāng)前狀態(tài)不允許取消!");
通過(guò)這兩個(gè)擴(kuò)展方法,我們不僅鞏固了函數(shù)式編程在減少代碼重復(fù)、提升代碼靈活性方面的優(yōu)勢(shì),還進(jìn)一步證明了通過(guò)抽象和泛型設(shè)計(jì),可以輕松應(yīng)對(duì)各種復(fù)雜的業(yè)務(wù)校驗(yàn)需求,使代碼更加貼近業(yè)務(wù)邏輯,易于理解和維護(hù)。
核心優(yōu)勢(shì)
- 代碼復(fù)用:通過(guò)泛型和函數(shù)式接口,該方法能夠適應(yīng)任何實(shí)體類和屬性的校驗(yàn)需求,大大減少了重復(fù)的查詢邏輯代碼。
- 清晰表達(dá)意圖:方法簽名直觀表達(dá)了校驗(yàn)邏輯的目的,提高了代碼的可讀性和可維護(hù)性。
- 靈活性:使用者只需提供幾個(gè)簡(jiǎn)單的 Lambda 表達(dá)式,即可完成復(fù)雜的查詢邏輯配置,無(wú)需關(guān)心底層實(shí)現(xiàn)細(xì)節(jié)。
- 易于維護(hù)與擴(kuò)展:
- 當(dāng)需要增加新的實(shí)體驗(yàn)證時(shí),僅需調(diào)用
ensureColumnValueValid
并傳入相應(yīng)的參數(shù),無(wú)需編寫(xiě)新的驗(yàn)證邏輯,降低了維護(hù)成本。 - 修改驗(yàn)證規(guī)則時(shí),只需調(diào)整
ensureColumnValueValid
內(nèi)部實(shí)現(xiàn),所有調(diào)用處自動(dòng)遵循新規(guī)則,便于統(tǒng)一管理。 - 異常處理集中于
ensureColumnValueValid
方法內(nèi)部,統(tǒng)一了異常拋出行為,避免了在多個(gè)地方處理相同的邏輯錯(cuò)誤,減少了潛在的錯(cuò)誤源。
- 當(dāng)需要增加新的實(shí)體驗(yàn)證時(shí),僅需調(diào)用
以上就是Java8利用Function接口減少代碼重復(fù)的操作指南的詳細(xì)內(nèi)容,更多關(guān)于Java8 Function減少代碼重復(fù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springboot配置mysql數(shù)據(jù)庫(kù)spring.datasource.url報(bào)錯(cuò)的解決
這篇文章主要介紹了springboot配置mysql數(shù)據(jù)庫(kù)spring.datasource.url報(bào)錯(cuò)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01mybatis中實(shí)現(xiàn)枚舉自動(dòng)轉(zhuǎn)換方法詳解
在使用mybatis的時(shí)候經(jīng)常會(huì)遇到枚舉類型的轉(zhuǎn)換,下面這篇文章主要給大家介紹了關(guān)于mybatis中實(shí)現(xiàn)枚舉自動(dòng)轉(zhuǎn)換的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-08-08SpringSecurity多表多端賬戶登錄的實(shí)現(xiàn)
本文主要介紹了SpringSecurity多表多端賬戶登錄的實(shí)現(xiàn)2024-05-05spring中的注解@@Transactional失效的場(chǎng)景代碼演示
這篇文章主要介紹了spring中的注解@@Transactional失效的場(chǎng)景代碼演示,@Transactional注解是Spring框架提供的用于聲明事務(wù)的注解,作用于類和方法上,需要的朋友可以參考下2024-01-01spring+maven實(shí)現(xiàn)發(fā)送郵件功能
這篇文章主要為大家詳細(xì)介紹了spring+maven實(shí)現(xiàn)發(fā)送郵件功能,利用spring提供的郵件工具來(lái)發(fā)送郵件,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07詳解Java8新特性Stream之list轉(zhuǎn)map及問(wèn)題解決
這篇文章主要介紹了詳解Java8新特性Stream之list轉(zhuǎn)map及問(wèn)題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09Java靜態(tài)內(nèi)部類實(shí)現(xiàn)單例過(guò)程
這篇文章主要介紹了Java靜態(tài)內(nèi)部類實(shí)現(xiàn)單例過(guò)程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10SpringBoot使用redis生成訂單號(hào)的實(shí)現(xiàn)示例
在電商系統(tǒng)中,生成唯一訂單號(hào)是常見(jiàn)需求,本文介紹如何利用SpringBoot和Redis實(shí)現(xiàn)訂單號(hào)的生成,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-09-09