這是一個或許對你有用的社群
《專案實戰(影片)》:從書中學,往事上“練” 《網際網路高頻面試題》:面朝簡歷學習,春暖花開 《架構 x 系統設計》:摧枯拉朽,掌控面試高頻場景題 《精進 Java 學習指南》:系統學習,網際網路主流技術棧 《必讀 Java 原始碼專欄》:知其然,知其所以然
這是一個或許對你有用的開源專案
國產 Star 破 10w+ 的開源專案,前端包括管理後臺 + 微信小程式,後端支援單體和微服務架構。功能涵蓋 RBAC 許可權、SaaS 多租戶、資料許可權、商城、支付、工作流、大屏報表、微信公眾號、ERP、CRM、AI 大模型等等功能:
Boot 多模組架構:https://gitee.com/zhijiantianya/ruoyi-vue-pro Cloud 微服務架構:https://gitee.com/zhijiantianya/yudao-cloud 影片教程:https://doc.iocoder.cn 【國內首批】支援 JDK 17/21 + SpringBoot 3.3、JDK 8/11 + Spring Boot 2.7 雙版本

前言
Java
開發的征途中,我們時常與重複程式碼不期而遇。這些重複程式碼不僅讓專案顯得笨重,更增加了維護成本。幸運的是,Java 8
帶來了函數語言程式設計的春風,以 Function
介面為代表的一系列新特性,為我們提供了破除這一難題的利劍。Java 8
的函數語言程式設計特性來重構資料有效性斷言邏輯,展示如何透過 SFunction
(基於 Java 8
的 Lambda
表示式封裝)減少程式碼重複,從而提升程式碼的優雅性和可維護性。基於 Spring Boot + MyBatis Plus + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
專案地址:https://github.com/YunaiV/ruoyi-vue-pro 影片教程:https://doc.iocoder.cn/video/
背景故事:資料校驗的煩惱
// 判斷使用者 ID 是否有效
publicvoidcheckUserExistence(String userId)
{
User user = userDao.findById(userId);
if
(user ==
null
) {
thrownew
RuntimeException(
"使用者ID無效"
);
}
}
// 判斷部門 ID 是否有效
publicvoidcheckDeptExistence(String deptId)
{
Dept dept = deptDao.findById(deptId);
if
(dept ==
null
) {
thrownew
RuntimeException(
"部門ID無效"
);
}
}
基於 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
專案地址:https://github.com/YunaiV/yudao-cloud 影片教程:https://doc.iocoder.cn/video/
Java 8 的魔法棒:函式式介面
Function<T, R>
是最基礎的代表,它接受一個型別 T
的輸入,返回型別 R
的結果。而在 MyBatis Plus
等框架中常用的 SFunction
是對 Lambda
表示式的進一步封裝,使得我們可以更加靈活地操作實體類的屬性。實戰演練:重構斷言方法
ensureColumnValueValid
方法正是利用了函式式介面的魅力,實現了對任意實體類指定列值的有效性斷言:
/**
* 確認資料庫欄位值有效(通用)
*
*
@param
<V> 待驗證值的型別
*
@param
valueToCheck 待驗證的值
*
@param
columnExtractor 實體類屬性提取函式
*
@param
queryExecutor 單條資料查詢執行器
*
@param
errorMessage 異常提示資訊模板
*/
publicstatic
<T, R, V>
voidensureColumnValueValid(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
)
thrownew
DataValidationException(String.format(errorMessage, valueToCheck));
}
對比分析
使用 Function
改造前
// 判斷使用者 ID 是否有效
publicvoidcheckUserExistence(String userId)
{
User user = userDao.findById(userId);
if
(user ==
null
) {
thrownew
RuntimeException(
"使用者ID無效"
);
}
}
// 判斷部門 ID 是否有效
publicvoidcheckDeptExistence(String deptId)
{
Dept dept = deptDao.findById(deptId);
if
(dept ==
null
) {
thrownew
RuntimeException(
"部門ID無效"
);
}
}
使用 Function
改造後
publicvoidassignTaskToUser(AddOrderDTO dto)
{
ensureColumnValueValid(dto.getUserId(), User::getId, userDao::getOne,
"使用者ID無效"
);
ensureColumnValueValid(dto.getDeptId(), Dept::getId, deptDao::getOne,
"部門ID無效"
);
ensureColumnValueValid(dto.getCustomerId(), Customer::getId, customerDao::getOne,
"客戶ID無效"
);
ensureColumnValueValid(dto.getDeptId(), Dept::getId, deptDao::getOne,
"部門ID無效"
);
ensureColumnValueValid(dto.getSupplieId(), Supplie::getId, supplierDao::getOne,
"供應商ID無效"
);
// 現在可以確信客戶存在
Customer cus = customerDao.findById(dto.getCustomerId());
// 建立訂單的邏輯...
}
優點
-
減少重複程式碼: 透過 ensureColumnValueValid
方法,所有涉及資料庫欄位值有效性檢查的地方都可以複用相同的邏輯,將變化的部分作為引數傳遞,大大減少了因特定校驗邏輯而產生的程式碼量。 -
增強程式碼複用: 抽象化的校驗方法適用於多種場景,無論是使用者ID、訂單號還是其他任何實體屬性的校驗,一套邏輯即可應對。 -
提升可讀性和維護性: 透過清晰的函式簽名和 Lambda 表示式,程式碼意圖一目瞭然,降低了後續維護的成本。 -
靈活性和擴充套件性: 當校驗規則發生變化時,只需要調整 ensureColumnValueValid
方法或其內部實現,所有呼叫該方法的地方都會自動受益,提高了系統的靈活性和擴充套件性。
舉一反三:拓展校驗邏輯的邊界
斷言指定列值等於預期值
validateColumnValueMatchesExpected
方法:
/**
* 驗證查詢結果中指定列的值是否與預期值匹配
*
*
@param
<T> 實體型別
*
@param
<R> 目標列值的型別
*
@param
<C> 查詢條件列值的型別
*
@param
targetColumn 目標列的提取函式,用於獲取想要驗證的列值
*
@param
expectedValue 期望的列值
*
@param
conditionColumn 條件列的提取函式,用於設定查詢條件
*
@param
conditionValue 條件列對應的值
*
@param
queryMethod 執行查詢的方法引用,返回單個實體物件
*
@param
errorMessage 驗證失敗時丟擲異常的錯誤資訊模板
*
@throws
RuntimeException 當查詢結果中目標列的值與預期值不匹配時丟擲異常
*/
publicstatic
<T, R, C>
voidvalidateColumnValueMatchesExpected
(
SFunction<T, R> targetColumn, R expectedValue,
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);
// 比較實際值與預期值是否匹配,這裡假設notMatch是一個自定義方法用於比較不匹配情況
boolean
doesNotMatch = notMatch(actualValue, expectedValue);
if
(doesNotMatch) {
// 若不匹配,則根據錯誤資訊模板丟擲異常
thrownew
RuntimeException(String.format(errorMessage, expectedValue, actualValue));
}
}
// 假設的輔助方法,用於比較值是否不匹配,根據實際需要實現
privatestatic
<R>
booleannotMatch(R actual, R expected)
{
// 示例簡單實現為不相等判斷,實際情況可能更復雜
return
!Objects.equals(actual, expected);
}
targetColumn
)、預期值(expectedValue
)、查詢條件列(conditionColumn
)及其對應的條件值(conditionValue
),並提供一個查詢方法(queryMethod
)來執行查詢。如果查詢到的列值與預期不符,則丟擲異常,錯誤資訊透過 errorMessage
引數定製。validateColumnValueMatchesExpected
方法來驗證使用者當前的角色是否確實為“普通使用者”。// 當用戶角色不是 “普通使用者” 時拋異常
validateColumnValueMatchesExpected(User::getRoleType,
"普通使用者"
, User::getId, userId, userMapper::getOne,
"使用者角色不是普通使用者,無法升級為管理員!"
);
斷言指定值位於期望值列表內
validateColumnValueMatchesExpectedList
方法:
/**
* 驗證查詢結果中指定列的值是否位於預期值列表內
*
*
@param
<T> 實體型別
*
@param
<R> 目標列值的型別
*
@param
<C> 查詢條件列值的型別
*
@param
targetColumn 目標列的提取函式,用於獲取想要驗證的列值
*
@param
expectedValueList 期望值的列表
*
@param
conditionColumn 條件列的提取函式,用於設定查詢條件
*
@param
conditionValue 條件列對應的值
*
@param
queryMethod 執行查詢的方法引用,返回單個實體物件
*
@param
errorMessage 驗證失敗時丟擲異常的錯誤資訊模板
*
@throws
RuntimeException 當查詢結果中目標列的值不在預期值列表內時丟擲異常
*/
publicstatic
<T, R, C>
voidvalidateColumnValueInExpectedList
(
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
)
thrownew
RuntimeException(
"列查詢結果為空"
);
if
(!expectedValueList.contains(actualValue)) {
thrownew
RuntimeException(errorMessage);
}
}
targetColumn
)、一個預期值列表(expectedValueList
)、查詢條件列(conditionColumn
)及其條件值(conditionValue
),同樣需要一個查詢方法(queryMethod
)。如果查詢到的列值不在預期值列表中,則觸發異常。validateColumnValueInExpectedList
方法能有效確保操作的合法性。// 假設 OrderStatusEnum 枚舉了所有可能的訂單狀態,cancelableStatuses 包含可取消的狀態
List<String> cancelableStatuses = Arrays.asList(OrderStatusEnum.WAITING_PAYMENT.getValue(), OrderStatusEnum.WAITING_DELIVERY.getValue());
// 驗證訂單狀態是否在可取消狀態列表內
validateColumnValueInExpectedList(Order::getStatus, cancelableStatuses, Order::getOrderId, orderId, orderMapper::selectOne,
"訂單當前狀態不允許取消!"
);
核心優勢
-
程式碼複用 :透過泛型和函式式介面,該方法能夠適應任何實體類和屬性的校驗需求,大大減少了重複的查詢邏輯程式碼。
-
清晰表達意圖 :方法簽名直觀表達了校驗邏輯的目的,提高了程式碼的可讀性和可維護性。
-
靈活性 :使用者只需提供幾個簡單的 Lambda 表示式,即可完成複雜的查詢邏輯配置,無需關心底層實現細節。
-
易於維護與擴充套件:
-
異常處理集中於 ensureColumnValueValid
方法內部,統一了異常丟擲行為,避免了在多個地方處理相同的邏輯錯誤,減少了潛在的錯誤源。 -
修改驗證規則時,只需調整 ensureColumnValueValid
內部實現,所有呼叫處自動遵循新規則,便於統一管理。 -
當需要增加新的實體驗證時,僅需呼叫 ensureColumnValueValid
並傳入相應的引數,無需編寫新的驗證邏輯,降低了維護成本。
函數語言程式設計的力量





