一文詳解Java Function的高級使用技巧
前言
來自企業(yè)級的一個經(jīng)驗總結(jié),每個技巧都經(jīng)過生產(chǎn)環(huán)境驗證,附帶完整上下文。
一、柯里化:函數(shù)返回函數(shù)的實戰(zhàn)價值
柯里化(Currying)在教材里總是 a -> b -> a + b 這種讓人打哈欠的例子。但在實際工程中,它解決的是一個非常具體的問題:如何在不修改框架代碼的前提下,讓調(diào)用方自由選擇"先定位到哪個服務(wù),再調(diào)哪個方法"。
看這個通用執(zhí)行器:
private <T, R> WeaResult<R> execute(
InvoiceParams<T> req,
Function<InvoiceFactory, Function<InvoiceParams<T>, WeaResult<R>>> methodSelector
) {
String invoiceType = resolveInvoiceType(req);
InvoiceFactory factory = invoiceFactory.getInstance(invoiceType);
if (factory == null) {
throw new RuntimeException("未找到開票工廠, invoiceType=" + invoiceType);
}
Function<InvoiceParams<T>, WeaResult<R>> handlerFunc = methodSelector.apply(factory);
if (handlerFunc == null) {
throw new RuntimeException("工廠 method 為 null, invoiceType=" + invoiceType);
}
try {
return handlerFunc.apply(req);
} catch (Exception e) {
throw new RuntimeException("執(zhí)行失敗: " + e.getMessage(), e);
}
}
methodSelector 的類型是 Function<InvoiceFactory, Function<InvoiceParams<T>, WeaResult<R>>>。拆開看:第一層根據(jù)工廠選出一個具體的處理函數(shù),第二層用這個函數(shù)處理請求。兩步走,但調(diào)用方可以用完全不同的方式填充這兩步。
方式一:完整的雙層 lambda,適合需要加額外邏輯的場景(加鎖、異常捕獲):
public WeaResult<MakeInvoiceResDTO> makeInvoice(InvoiceParams<MakeInvoiceReqDTO> inputParams) {
return execute(inputParams, invoiceFactory -> e -> {
try {
boolean flag = safeLockService.tryLock(buildInvoiceSafeLockEntity(e));
if (!flag) return WeaResult.fail(SystemEnv.getHtmlLabelName(342919, "加鎖失敗,請稍后在試"), true);
return invoiceFactory.getMakeInvoice().makeInvoice(e);
} catch (DuplicateKeyException ex) {
return WeaResult.fail(5012, buildRepeatMsg(e), true);
} catch (Exception ex) {
if (isSocketTimeoutException(ex)) {
log.warn(">>>>>>SocketTimeoutException,返回成功+開票中,bizId={}, msg={}", e.getInvoiceReq().getExternalDocumentNo(), ex.getMessage());
return WeaResult.success(MakeInvoiceResDTO.builder().returnCode("00").returnMessage(SystemEnv.getHtmlLabelName(343981, "恭喜你,開票請求提交成功!") + ",ErrorMsg:" + ex.getMessage()).build());
}
return WeaResult.fail(ex.getMessage(), true);
}
});
}
方式二:一行方法引用鏈,適合純轉(zhuǎn)發(fā)的場景:
public WeaResult<RedConfirmationApplyResDTO> redMakeInvoiceApply(InvoiceParams<RedConfirmationApplyReqDTO> inputParams) {
return execute(inputParams, factory -> factory.getRedInvoice()::redConfirmationApply);
}
public WeaResult<CloudTitleInfo> getEnterpriseInfo(InvoiceParams<String> inputParams) {
return execute(inputParams, factory -> factory.getEnterpriseDetail()::getEnterpriseInfo);
}
factory -> factory.getRedInvoice()::redConfirmationApply 的含義是:拿到工廠后,先取出紅字發(fā)票服務(wù)(getRedInvoice()),再綁定其 redConfirmationApply 方法作為最終的處理函數(shù)。這就是柯里化的實際價值——一個 execute 方法同時支撐了"開票加鎖 + 異常降級"和"紅沖直接轉(zhuǎn)發(fā)"兩種截然不同的調(diào)用模式,零代碼重復(fù)。
如果不用柯里化,你需要寫一個 executeWithLock、一個 executeSimple、一個 executeWithRetry……每加一種變體就多一個方法。
二、EnumMap + Supplier:干掉 switch-case 的正確方式
大家都知道可以用 Map 替代 switch,但很多人用的是 Map<Enum, String> 這種存靜態(tài)值的方式。真正有用的是 Map<Enum, Supplier<T>>——存的不是值,而是"算值的過程"。
public RuleCommonDTO getConfig(RuleEnum ruleEnum, CreditConditionRuleContext context) {
Map<RuleEnum, Supplier<RuleCommonDTO>> supplierOfMap = new EnumMap<>(RuleEnum.class);
supplierOfMap.put(RuleEnum.COMPANY, () -> buildRuleCommonDTO(
ebDataSupport.getCompanyConfig(context.getCompanyTaxNo()),
CompanyConfig::getRemindTypes,
CompanyConfig::getRemindObjs,
CompanyConfig::getCompanyName
));
supplierOfMap.put(RuleEnum.CUSTOMER, () -> buildRuleCommonDTO(
ebDataSupport.getCustomerConfig(context.getCompanyTaxNo(), context.getCusTaxNo()),
CustomerConfig::getRemindTypes,
CustomerConfig::getRemindObjs,
CustomerConfig::getCustomerName
));
supplierOfMap.put(RuleEnum.USER, () -> buildRuleCommonDTO(
ebDataSupport.getUserConfig(context.getCompanyTaxNo(), context.getApplyMakeInvoiceUser()),
UserConfig::getRemindTypes,
UserConfig::getRemindObjs,
UserConfig::getEmployeeName
));
return Optional.ofNullable(supplierOfMap.get(ruleEnum))
.map(Supplier::get)
.orElseThrow(() -> new IllegalArgumentException(
SystemEnv.getHtmlLabelName(299572, "無效的 RuleEnum 值: ") + ruleEnum)
);
}
關(guān)鍵點在于:三個 Supplier 中的 ebDataSupport.getCompanyConfig()、getCustomerConfig()、getUserConfig() 都是數(shù)據(jù)庫查詢。如果用普通 Map 存值,三個查詢會在方法入口全部執(zhí)行。用 Supplier 包裝后,只有命中的那個枚舉對應(yīng)的查詢才會真正執(zhí)行。
配合最后的 Optional.ofNullable(...).map(Supplier::get).orElseThrow(...) 三連,既做了空值防御又做了延遲求值,還給了明確的錯誤提示。
再看 buildRuleCommonDTO 這個被調(diào)用的方法:
private <T> RuleCommonDTO buildRuleCommonDTO(T config,
Function<T, List<String>> remindTypesFunc,
Function<T, List<SubFieldDTO>> remindObjsFunc,
Function<T, String> nameFunc) {
return Optional.ofNullable(config)
.map(e -> RuleCommonDTO.builder()
.remindTypes(remindTypesFunc.apply(e))
.remindObjs(remindObjsFunc.apply(e))
.name(nameFunc.apply(e))
.build()
).orElse(null);
}
三個 Function<T, ?> 參數(shù)讓這個方法可以處理 CompanyConfig、CustomerConfig、UserConfig 三種完全不同的類型,調(diào)用方只需要傳對應(yīng)的方法引用。一個私有方法干了本來需要三個方法或者一堆 instanceof 才能干的事。
三、ImmutableMap + BiConsumer:服務(wù)路由表
當系統(tǒng)有 9 個不同的自動化服務(wù)需要根據(jù)能力碼分發(fā)時,if-else 或 switch 會變成災(zāi)難??催@個做法:
ImmutableMap<String, BiConsumer<MakeInvoiceCompanyInfo, MakeInvoiceCompanyInfo>>
serviceMap = ImmutableMap.of(
A_203059, (a, b) -> autoVerifyService.execute(a, b, authorCode, interKey),
A_203067, (a, b) -> autoAggregationService.execute(a, b, authorCode, interKey),
A_203057, (a, b) -> autoEntryService.execute(a, b, authorCode, interKey),
A_203065, (a, b) -> autoDeductionService.execute(a, b, authorCode, interKey),
A_202044, (a, b) -> autoJZFWMakeInvoiceService.execute(a, b, authorCode, interKey),
A_202046, (a, b) -> autoBDCXSMakeInvoiceService.execute(a, b, authorCode, interKey),
A_202038, (a, b) -> autoBDCZLMakeInvoiceService.execute(a, b, authorCode, interKey),
A_202007, (a, b) -> autoMakeInvoiceService.execute(a, b, authorCode, interKey),
A_202031, (a, b) -> autoCropMakeInvoiceService.execute(a, b, authorCode, interKey)
);
abilityCodes.stream()
.filter(serviceMap::containsKey)
.forEach(scene ->
executeServiceSafely(
() -> Optional.ofNullable(serviceMap.get(scene))
.ifPresent(e -> e.accept(originCompanyInfo, virtualCompanyInfo))
)
);
有三個細節(jié)值得關(guān)注:
- 閉包捕獲。每個
BiConsumer<MakeInvoiceCompanyInfo, MakeInvoiceCompanyInfo>的 lambda 體內(nèi)都引用了外部的authorCode和interKey。這兩個變量是"固定參數(shù)"(對于本次調(diào)用而言),而a, b是"動態(tài)參數(shù)"。通過閉包,你自然地實現(xiàn)了部分應(yīng)用(Partial Application)——把四參方法變成了雙參的BiConsumer。 serviceMap::containsKey作為 filter 的謂詞。這比scene -> serviceMap.containsKey(scene)更簡潔,而且語義更清晰——“過濾出路由表中存在的場景”。executeServiceSafely包裝 Runnable。每個服務(wù)的執(zhí)行都被 try-catch 包裹,任何一個服務(wù)拋異常不會影響其他服務(wù)。這個模式的價值在于:異常隔離邏輯只寫一次,9 個服務(wù)自動受益。
四、函數(shù)裝飾器:用 Function 參數(shù)實現(xiàn) AOP
AbstractAutoSupport 中有兩個方法,它們的關(guān)系揭示了函數(shù)式編程中最實用的模式之一——函數(shù)裝飾器。
第一層:doInvokeMethod,給任意 Function 加上狀態(tài)追蹤和異常捕獲:
protected <T, R> R doInvokeMethod(InvoiceParams<T> params,
String interfaceKey,
Function<InvoiceParams<T>, WeaResult<R>> function) {
WeaResult<R> weaResult = null;
String errorMsg = "";
AtomicReference<String> status = new AtomicReference<>("0");
try {
weaResult = function.apply(params);
R data = weaResult.getData();
checkIsSuccess(status, data, "no-init");
compressDataStream(data);
return data;
} catch (Exception e) {
setFail(status);
errorMsg = e.getMessage();
} finally {
doUpdateState(interfaceKey, params, weaResult, errorMsg, status.get());
}
return null;
}
第二層:retryOf3Times,給任意 Supplier 加上重試能力:
protected <T> void retryOf3Times(Supplier<T> supplier) {
for (int i = 0; i < 3; i++) {
T t = supplier.get();
if (canApplyNext(t)) {
break;
}
sleep3Mins();
}
}
在實際調(diào)用處,兩層嵌套:
retryOf3Times(() -> doInvokeMethod(build, getInterfaceKey(...), invoiceDeduction::queryApplyStatisticsStatus));
從內(nèi)到外讀:invoiceDeduction::queryApplyStatisticsStatus 是原始業(yè)務(wù)方法,被 doInvokeMethod 裝飾后獲得了"狀態(tài)追蹤 + 異常捕獲 + 數(shù)據(jù)壓縮"的能力,再被 retryOf3Times 裝飾后獲得了"失敗重試"的能力。
這就是窮人版的 AOP。 不需要 AspectJ,不需要注解處理器,不需要動態(tài)代理。一個 Function 參數(shù)就夠了。而且它比 AOP 更好的地方是——調(diào)用方在寫代碼的時候,能清清楚楚地看到這個方法被加了哪些裝飾,不存在"隱式增強"的困惑。
更進一步,executeAndCache 是另一個裝飾器,增加了"成功后緩存結(jié)果"的能力:
protected <T, R> R executeAndCache(InvoiceParams<T> build, String interfaceKey, String cacheKey,
Function<InvoiceParams<T>, WeaResult<R>> function,
Function<R, List<?>> getListFunc) {
WeaResult<R> weaResult = null;
String errorMsg = "";
AtomicReference<String> status = new AtomicReference<>("0");
try {
weaResult = function.apply(build);
R data = weaResult.getData();
Optional.ofNullable(data)
.map(getListFunc)
.filter(list -> !list.isEmpty())
.ifPresent(e -> CacheUtils.put(cacheKey, JSON.toJSONString(data)));
checkIsSuccess(status, data, "init");
compressDataStream(data);
return data;
} catch (Exception e) {
setFail(status);
errorMsg = e.getMessage();
} finally {
doUpdateState(interfaceKey, build, weaResult, errorMsg, status.get());
}
return null;
}
注意第二個 Function<R, List<?>> getListFunc 參數(shù)——它的職責是從返回結(jié)果中提取"是否有有效數(shù)據(jù)"的判斷依據(jù)。只有當提取出的列表非空時,才寫緩存。這讓緩存條件完全由調(diào)用方控制,executeAndCache 本身不需要知道 R 的具體結(jié)構(gòu)。
五、Supplier + 分布式鎖:資源管控的函數(shù)式寫法
public <T> WeaResult<T> withLock(String key, int timeout, Supplier<WeaResult<T>> action) {
boolean locked = false;
try {
locked = distributionLock.isSuccessTryLock(key, timeout);
if (!locked) {
return WeaResult.fail(SystemEnv.getHtmlLabelName(321088, "當前操作頻繁,請稍后再試") + ":" + key, true);
}
return action.get();
} catch (Exception ex) {
log.error(">>>>>>Error execute withLock, key={}", key, ex);
return WeaResult.fail(SystemEnv.getHtmlLabelName(321091, "操作失敗,請稍后再試") + ":" + key, true);
} finally {
if (locked) {
try {
distributionLock.unLock(key);
} catch (Exception e) {
log.error(">>>>>>Error unlocking, key={}", key, e);
}
}
}
}
不用 Supplier,你要么把鎖邏輯散落到每個調(diào)用方(復(fù)制粘貼 try-finally-unlock),要么搞一套 AOP + 注解的方案。Supplier<WeaResult<T>> 讓這件事變成了一行調(diào)用:
return invLockUtils.withLock("make_invoice_" + bizId, () -> {
// 你的業(yè)務(wù)邏輯
return doMakeInvoice(params);
});
注意 finally 塊中的 if (locked) 判斷——如果加鎖本身就失敗了(locked = false),不能去解鎖一個你沒持有的鎖。這個細節(jié)在手寫鎖時很容易漏掉,而封裝成 withLock 后,這種邊界處理只需要正確實現(xiàn)一次。
六、Function 做泛型數(shù)據(jù)適配器
當系統(tǒng)需要從同一個底層數(shù)據(jù)源(EB 表單)中讀取完全不同結(jié)構(gòu)的配置時,Function<Map<String, FieldDTO>, T> 充當了"數(shù)據(jù)適配器"的角色:
private <T> T getConfig(String tableIdKey, QueryConditionWrapper conditionWrapper, Function<Map<String, FieldDTO>, T> configMapper) {
try {
String tableId = ebFormObjUtil.getTableIdSync(tableIdKey);
List<Map<String, FieldDTO>> configData = ebFormFieldService.getValueByConditionTree(tableId, conditionWrapper);
return Optional.ofNullable(configData)
.filter(data -> !data.isEmpty())
.map(data -> configMapper.apply(data.get(0)))
.orElse(null);
} catch (Exception ex) {
log.error("Error in getConfig for tableIdKey {}: {}", tableIdKey, ex.getMessage(), ex);
return null;
}
}
調(diào)用方傳入的 lambda 是一個完整的建造過程——從原始 Map 到強類型 DTO:
public CreditGlobalConfig getCreditGlobalConfig() {
return getConfig(TagConstant.UF_INV_LIMIT_CREDIT_SETTING,
new QueryConditionWrapper()
.eq("xeszzkg", "1")
.eq("zhkey", tenantKey())
.eq("yyid", String.valueOf(ebFormObjUtil.getAppId(TagConstant.CS_APPID_TAG, tenantKey()))),
data -> CreditGlobalConfig.builder()
.globalSwitch(FieldUtils.getValueIfPresent(data.get("xeszzkg")).orElse("0"))
.appId(String.valueOf(ebFormObjUtil.getAppId(TagConstant.CS_APPID_TAG, tenantKey())))
.tenantKey(tenantKey())
.customerSwitch(FieldUtils.getValue(data.get("kg_2aji")))
.customerAmount(FieldUtils.getValue(data.get("khwdmryje")))
.warnType(FieldUtils.getFirstValue(data.get("kzfs")))
.build()
);
}
每個 getXxxConfig() 方法共享完全相同的"查表 → 取第一條 → 轉(zhuǎn)換 → 空值兜底"流程,只有最后的"轉(zhuǎn)換"步驟不同。Function 參數(shù)讓你做到了"流程復(fù)用,差異外置"——這是模板方法模式的函數(shù)式重寫,不需要繼承。
七、EnumMap + Function + CompletableFuture:并行策略引擎
private boolean matchRules(CreditConditionRuleContext context, MatchType matchType) {
if (CollectionUtils.isEmpty(conditionRuleList)) return false;
Map<MatchType, Function<List<InvCreditConditionRuleService>, Boolean>> matchStrategies = new EnumMap<>(MatchType.class);
matchStrategies.put(MatchType.ANY, rules -> anyMatch(context, rules));
matchStrategies.put(MatchType.ALL, rules -> rules.stream().allMatch(rule -> rule.match(context)));
return matchStrategies.getOrDefault(matchType, rules -> false).apply(reSorted(conditionRuleList));
}
private static boolean anyMatch(CreditConditionRuleContext context, List<InvCreditConditionRuleService> rules) {
ExecutorService executor = InvThreadPoolUtil.workBizThreadPool;
List<CompletableFuture<Boolean>> futures = rules.stream()
.map(rule -> CompletableFuture.supplyAsync(() -> rule.match(context), executor))
.collect(Collectors.toList());
return futures.stream().anyMatch(CompletableFuture::join);
}
這段代碼值得拆解的地方在于:
MatchType.ANY對應(yīng)的策略函數(shù)內(nèi)部用了CompletableFuture.supplyAsync做并行匹配——多條規(guī)則同時跑,任一命中即返回。MatchType.ALL對應(yīng)的策略函數(shù)用了串行的allMatch——必須全部滿足,短路求值。getOrDefault(matchType, rules -> false)提供了一個安全的 fallback 函數(shù)。不是返回null,不是拋異常,而是返回一個"永遠返回 false"的函數(shù)。
注意 rules -> false 中的 rules 參數(shù)根本沒被使用——這是一個故意忽略輸入的函數(shù),它的存在只是為了滿足 Function<List<...>, Boolean> 的簽名。這種寫法在函數(shù)式編程中叫做 “常量函數(shù)”(Constant Function),比 null 判斷優(yōu)雅得多。
八、Function + BiFunction:兩步式服務(wù)編排
protected <V, S, R> WeaResult<R> handleRequest(
ApiParams<V> request,
InvCompanyInfoService companyInfoService,
Function<MakeInvoiceCompanyInfo, S> serviceResolver,
BiFunction<S, InvoiceParams<V>, WeaResult<R>> handler
) {
WeaResult<R> apply = null;
try {
echoInfoLog(request);
setTargetCurrentEmployeeId(request);
V invoiceReq = request.getInvoiceReq();
MakeInvoiceCompanyInfo companyInfo = companyInfoService.getInvCompanyInfoByTaxNo(getTaxNo(invoiceReq));
InvoiceParams<V> params = InvoiceParams.<V>builder()
.companyInfo(companyInfo)
.invoiceReq(invoiceReq)
.build();
S service = serviceResolver.apply(companyInfo);
apply = handler.apply(service, params);
} catch (Exception ex) {
log.error(">>>>>>Error when handleRequest ex:{}", ex.getMessage(), ex);
return WeaResult.fail(ex.getMessage(), true);
} finally {
TenantRpcContext.removeTargetTenantKey();
TenantRpcContext.removeTargetEmployeeId();
}
return apply;
}
三個泛型參數(shù) <V, S, R> 分別代表:請求體類型、服務(wù)類型、響應(yīng)類型。兩個函數(shù)參數(shù)的分工:
Function<MakeInvoiceCompanyInfo, S> serviceResolver:根據(jù)公司信息決定用哪個服務(wù)實例(可能是百望、可能是航信、可能是樂企)。BiFunction<S, InvoiceParams<V>, WeaResult<R>> handler:拿到服務(wù)實例后,執(zhí)行具體的業(yè)務(wù)操作。
這種設(shè)計把"選哪個服務(wù)"和"用服務(wù)做什么"徹底解耦。調(diào)用方類似:
return handleRequest(request, companyInfoService,
companyInfo -> instanceFetcher, // 第一步:選服務(wù)
(fetcher, params) -> fetcher.makeInvoice(params) // 第二步:調(diào)方法
);
而框架層的 handleRequest 負責了所有調(diào)用方不想關(guān)心的事:日志、租戶上下文設(shè)置、異常捕獲、上下文清理。
九、Optional 鏈的工程邊界
Optional 鏈好用,但有邊界。先看一個好的例子——清晰的逐層解包:
private String resolveInvoiceType(InvoiceParams<?> req) {
return Optional.ofNullable(req.getCompanyInfo())
.map(MakeInvoiceCompanyInfo::getThirdType)
.map(InvoiceThirdType::getInvoiceType)
.orElseThrow(() ->
new RuntimeException("缺失 thirdType 字段: " + JSON.toJSONString(req.getCompanyInfo())));
}
每一步 .map() 都是一個明確的屬性訪問,讀起來像導(dǎo)航路徑:req → companyInfo → thirdType → invoiceType。
再看一個巧妙的帶 flatMap 的 Optional 鏈:
private List<UserEntity> getReceivers(RuleCommonDTO commonDTO, Function<List<SubFieldDTO>, List<UserEntity>> toUserEntityList) {
List<String> remindTypes = commonDTO.getRemindTypes();
List<SubFieldDTO> remindObjs = commonDTO.getRemindObjs();
return Optional.ofNullable(remindTypes)
.filter(e -> e.stream().anyMatch("0"::equals))
.flatMap(e -> Optional.ofNullable(remindObjs)
.filter(r -> !r.isEmpty()))
.map(r -> toUserEntityList.apply(remindObjs))
.orElse(new ArrayList<>());
}
這段鏈做了三件事:① 檢查提醒類型中是否包含 “0”(指定接收人模式),② 檢查接收人列表是否非空,③ 用傳入的 Function 把原始數(shù)據(jù)轉(zhuǎn)成 UserEntity。三個判斷條件任一不滿足,整條鏈短路返回空列表。 這比三層嵌套 if 緊湊得多。
但 Optional 也有濫用的時候。代碼庫里有這樣的寫法:
Integer type = Optional.ofNullable(
Optional.ofNullable(
Optional.ofNullable(invoice_info.getCommInfo())
.orElse(new InvoiceCommInfo()).getPro())
.orElse(new InvoiceProVo()).getType())
.orElse(0);
三層嵌套的 Optional.ofNullable(...).orElse(new Xxx()).getYyy(),每層都在創(chuàng)建空對象作為 fallback 然后立刻調(diào)方法。這不叫函數(shù)式,這叫折磨。更清晰的寫法是:
Integer type = Optional.ofNullable(invoice_info.getCommInfo())
.map(InvoiceCommInfo::getPro)
.map(InvoiceProVo::getType)
.orElse(0);
經(jīng)驗法則:Optional 鏈中如果出現(xiàn)了 orElse(new Xxx()).getYyy() 這種"創(chuàng)建臨時對象只為鏈式調(diào)用"的寫法,說明你應(yīng)該用 .map() 替代。
十、反射 vs Function:一個反面教材
protected <R> boolean canApplyNext(R data) {
return Optional.ofNullable(data)
.map(d -> {
try {
return (String) d.getClass().getMethod("getReturnCode").invoke(d);
} catch (Exception ex) {
return null;
}
})
.filter("00"::equals).isPresent();
}
這個方法對任意類型 R 的對象反射調(diào)用 getReturnCode()。因為 R 沒有上界約束,編譯器不知道 R 有沒有這個方法,只能在運行時用反射碰運氣。
改法很直接——加一個 Function 參數(shù):
protected <R> boolean canApplyNext(R data, Function<R, String> codeExtractor) {
return Optional.ofNullable(data)
.map(codeExtractor)
.filter("00"::equals)
.isPresent();
}
// 調(diào)用
canApplyNext(result, BWBaseRes::getReturnCode);
反射調(diào)用一次大約 50~100ns,方法引用調(diào)用大約 5ns,差一個數(shù)量級。但性能不是重點——重點是編譯期安全。反射版本在方法名打錯(比如 getReturncode vs getReturnCode)時只會在運行時 NPE,而 Function 版本會在編譯時就報錯。
總結(jié):Function 高級用法的幾條原則
1. 柯里化不是炫技,是分層抽象的工具。 當你的框架需要"兩步定位"(先選服務(wù)再選方法),Function<A, Function<B, R>> 比任何設(shè)計模式都簡潔。
2. Map<Enum, Supplier> 比 switch-case 優(yōu)越的地方不在于"消除了 switch",而在于實現(xiàn)了延遲求值。 只有命中的分支才會執(zhí)行計算。
3. 函數(shù)裝飾器是輕量級 AOP。 doInvokeMethod(params, key, actualMethod) 這種寫法讓橫切關(guān)注點(日志、狀態(tài)、異常)透明地疊加在業(yè)務(wù)函數(shù)上,且調(diào)用方完全可見、可控。
4. Supplier + 資源管理(鎖、連接、事務(wù))是 try-with-resources 的泛化版本。 把"需要被保護的代碼"包成 Supplier,在外層統(tǒng)一處理獲取-釋放-異常。
5. Function<RawData, T> 做數(shù)據(jù)適配器,讓一個讀取方法服務(wù)于 N 種目標類型。 這是泛型和函數(shù)式結(jié)合最自然的場景。
6. 常量函數(shù) x -> defaultValue 作為 getOrDefault 的 fallback,比 null 判斷安全。 它保證了返回值永遠是合法的函數(shù),調(diào)用方不需要做空檢查。
7. Optional 鏈的正確用法是 .map().map().orElse(),不是 .orElse(new Xxx()).getYyy()。 后者本質(zhì)上是在濫用 Optional 來模擬空安全運算符,創(chuàng)建了一堆無意義的臨時對象。
以上就是一文詳解Java Function的高級使用技巧的詳細內(nèi)容,更多關(guān)于Java Function使用技巧的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java?ServletContext與ServletConfig接口使用教程
ServletConfig對象,叫Servlet配置對象。主要用于加載配置文件的初始化參數(shù)。我們知道一個Web應(yīng)用里面可以有多個servlet,如果現(xiàn)在有一份數(shù)據(jù)需要傳給所有的servlet使用,那么我們就可以使用ServletContext對象了2022-09-09
Java遞歸讀取文件例子_動力節(jié)點Java學(xué)院整理
本文通過一段示例代碼給大家介紹了java遞歸讀取文件的方法,代碼簡單易懂,非常不錯,具有參考借鑒價值,需要的朋友參考下吧2017-05-05
springboot集成sensitive-word實現(xiàn)敏感詞過濾的兩種方案
敏感詞過濾通常是指從文本中檢測并移除或替換掉被認為是不適當、冒犯性或違反特定社區(qū)準則的詞匯,這篇文章主要介紹了springboot集成sensitive-word實現(xiàn)敏感詞過濾,需要的朋友可以參考下2024-08-08
SpringBoot整合Mybatis,解決TypeAliases配置失敗的問題
這篇文章主要介紹了SpringBoot整合Mybatis,解決TypeAliases配置失敗的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07
mybatis-plus 攔截器敏感字段加解密的實現(xiàn)
數(shù)據(jù)庫在保存數(shù)據(jù)時,對于某些敏感數(shù)據(jù)需要脫敏或者加密處理,本文主要介紹了mybatis-plus 攔截器敏感字段加解密的實現(xiàn),感興趣的可以了解一下2021-11-11
基于Jackson實現(xiàn)API接口數(shù)據(jù)脫敏的示例詳解
用戶的一些敏感數(shù)據(jù),例如手機號、郵箱、身份證等信息,在數(shù)據(jù)庫以明文存儲,但在接口返回數(shù)據(jù)給瀏覽器(或三方客戶端)時,希望對這些敏感數(shù)據(jù)進行脫敏,所以本文就給大家介紹以惡如何利用Jackson實現(xiàn)API接口數(shù)據(jù)脫敏,需要的朋友可以參考下2023-08-08

