前言
来自企业级的一个经验总结,每个技巧都经过生产环境验证,附带完整上下文。
一、柯里化:函数返回函数的实战价值
柯里化(Currying)在教材里总是 a -> b -> a + b 这种让人打哈欠的例子。但在实际工程中,它解决的是一个非常具体的问题:如何在不修改框架代码的前提下,让调用方自由选择"先定位到哪个服务,再调哪个方法"。
看这个通用执行器:
164:185:weaver-inc-biz-service/src/main/java/com/weaver/inc/book/manager/business/adapter/engine/InstanceFetcher.java
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("执行失败: " + e.getMessage(), e);
}
}
methodSelector 的类型是 Function<InvoiceFactory, Function<InvoiceParams<T>, WeaResult<R>>>。拆开看:第一层根据工厂选出一个具体的处理函数,第二层用这个函数处理请求。两步走,但调用方可以用完全不同的方式填充这两步。
方式一:完整的双层 lambda,适合需要加额外逻辑的场景(加锁、异常捕获):
60:79:weaver-inc-biz-service/src/main/java/com/weaver/inc/book/manager/business/adapter/engine/InstanceFetcher.java
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);
}
});
}
方式二:一行方法引用链,适合纯转发的场景:
152:157:weaver-inc-biz-service/src/main/java/com/weaver/inc/book/manager/business/adapter/engine/InstanceFetcher.java
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 的含义是:拿到工厂后,先取出红字发票服务(getRedInvoice()),再绑定其 redConfirmationApply 方法作为最终的处理函数。这就是柯里化的实际价值------一个 execute 方法同时支撑了"开票加锁 + 异常降级"和"红冲直接转发"两种截然不同的调用模式,零代码重复。
如果不用柯里化,你需要写一个 executeWithLock、一个 executeSimple、一个 executeWithRetry......每加一种变体就多一个方法。
二、EnumMap + Supplier:干掉 switch-case 的正确方式
大家都知道可以用 Map 替代 switch,但很多人用的是 Map<Enum, String> 这种存静态值的方式。真正有用的是 Map<Enum, Supplier<T>>------存的不是值,而是"算值的过程"。
62:92:weaver-inc-biz-service/src/main/java/com/weaver/inc/book/service/impl/CreditMessageServiceImpl.java
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)
);
}
关键点在于:三个 Supplier 中的 ebDataSupport.getCompanyConfig()、getCustomerConfig()、getUserConfig() 都是数据库查询。如果用普通 Map 存值,三个查询会在方法入口全部执行。用 Supplier 包装后,只有命中的那个枚举对应的查询才会真正执行。
配合最后的 Optional.ofNullable(...).map(Supplier::get).orElseThrow(...) 三连,既做了空值防御又做了延迟求值,还给了明确的错误提示。
再看 buildRuleCommonDTO 这个被调用的方法:
94:105:weaver-inc-biz-service/src/main/java/com/weaver/inc/book/service/impl/CreditMessageServiceImpl.java
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, ?> 参数让这个方法可以处理 CompanyConfig、CustomerConfig、UserConfig 三种完全不同的类型,调用方只需要传对应的方法引用。一个私有方法干了本来需要三个方法或者一堆 instanceof 才能干的事。
三、ImmutableMap + BiConsumer:服务路由表
当系统有 9 个不同的自动化服务需要根据能力码分发时,if-else 或 switch 会变成灾难。看这个做法:
184:204:weaver-inc-biz-service/src/main/java/com/weaver/inc/book/manager/auto/AutoManagerService.java
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))
)
);
有三个细节值得关注:
-
闭包捕获 。每个
BiConsumer<MakeInvoiceCompanyInfo, MakeInvoiceCompanyInfo>的 lambda 体内都引用了外部的authorCode和interKey。这两个变量是"固定参数"(对于本次调用而言),而a, b是"动态参数"。通过闭包,你自然地实现了部分应用(Partial Application) ------把四参方法变成了双参的BiConsumer。 -
serviceMap::containsKey作为 filter 的谓词。这比scene -> serviceMap.containsKey(scene)更简洁,而且语义更清晰------"过滤出路由表中存在的场景"。 -
executeServiceSafely包装 Runnable。每个服务的执行都被 try-catch 包裹,任何一个服务抛异常不会影响其他服务。这个模式的价值在于:异常隔离逻辑只写一次,9 个服务自动受益。
四、函数装饰器:用 Function 参数实现 AOP
AbstractAutoSupport 中有两个方法,它们的关系揭示了函数式编程中最实用的模式之一------函数装饰器。
第一层:doInvokeMethod,给任意 Function 加上状态追踪和异常捕获:
265:284:weaver-inc-biz-service/src/main/java/com/weaver/inc/book/manager/auto/AbstractAutoSupport.java
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 加上重试能力:
89:97:weaver-inc-biz-service/src/main/java/com/weaver/inc/book/manager/auto/AbstractAutoSupport.java
protected <T> void retryOf3Times(Supplier<T> supplier) {
for (int i = 0; i < 3; i++) {
T t = supplier.get();
if (canApplyNext(t)) {
break;
}
sleep3Mins();
}
}
在实际调用处,两层嵌套:
java
retryOf3Times(() -> doInvokeMethod(build, getInterfaceKey(...), invoiceDeduction::queryApplyStatisticsStatus));
从内到外读:invoiceDeduction::queryApplyStatisticsStatus 是原始业务方法,被 doInvokeMethod 装饰后获得了"状态追踪 + 异常捕获 + 数据压缩"的能力,再被 retryOf3Times 装饰后获得了"失败重试"的能力。
这就是穷人版的 AOP。 不需要 AspectJ,不需要注解处理器,不需要动态代理。一个 Function 参数就够了。而且它比 AOP 更好的地方是------调用方在写代码的时候,能清清楚楚地看到这个方法被加了哪些装饰,不存在"隐式增强"的困惑。
更进一步,executeAndCache 是另一个装饰器,增加了"成功后缓存结果"的能力:
173:196:weaver-inc-biz-service/src/main/java/com/weaver/inc/book/manager/auto/AbstractAutoSupport.java
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 参数------它的职责是从返回结果中提取"是否有有效数据"的判断依据。只有当提取出的列表非空时,才写缓存。这让缓存条件完全由调用方控制,executeAndCache 本身不需要知道 R 的具体结构。
五、Supplier + 分布式锁:资源管控的函数式写法
34:54:weaver-inc-biz-service/src/main/java/com/weaver/inc/book/util/InvLockUtils.java
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,你要么把锁逻辑散落到每个调用方(复制粘贴 try-finally-unlock),要么搞一套 AOP + 注解的方案。Supplier<WeaResult<T>> 让这件事变成了一行调用:
java
return invLockUtils.withLock("make_invoice_" + bizId, () -> {
// 你的业务逻辑
return doMakeInvoice(params);
});
注意 finally 块中的 if (locked) 判断------如果加锁本身就失败了(locked = false),不能去解锁一个你没持有的锁。这个细节在手写锁时很容易漏掉,而封装成 withLock 后,这种边界处理只需要正确实现一次。
六、Function 做泛型数据适配器
当系统需要从同一个底层数据源(EB 表单)中读取完全不同结构的配置时,Function<Map<String, FieldDTO>, T> 充当了"数据适配器"的角色:
43:56:weaver-inc-biz-service/src/main/java/com/weaver/inc/book/service/ConditionRuleEBDataSupport.java
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;
}
}
调用方传入的 lambda 是一个完整的建造过程------从原始 Map 到强类型 DTO:
74:89:weaver-inc-biz-service/src/main/java/com/weaver/inc/book/service/ConditionRuleEBDataSupport.java
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() 方法共享完全相同的"查表 → 取第一条 → 转换 → 空值兜底"流程,只有最后的"转换"步骤不同。Function 参数让你做到了"流程复用,差异外置"------这是模板方法模式的函数式重写,不需要继承。
七、EnumMap + Function + CompletableFuture:并行策略引擎
65:97:weaver-inc-biz-service/src/main/java/com/weaver/inc/book/service/impl/InvConditionRuleEngineImpl.java
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对应的策略函数内部用了CompletableFuture.supplyAsync做并行匹配------多条规则同时跑,任一命中即返回。MatchType.ALL对应的策略函数用了串行的allMatch------必须全部满足,短路求值。getOrDefault(matchType, rules -> false)提供了一个安全的 fallback 函数。不是返回null,不是抛异常,而是返回一个"永远返回 false"的函数。
注意 rules -> false 中的 rules 参数根本没被使用------这是一个故意忽略输入的函数,它的存在只是为了满足 Function<List<...>, Boolean> 的签名。这种写法在函数式编程中叫做 "常量函数"(Constant Function) ,比 null 判断优雅得多。
八、Function + BiFunction:两步式服务编排
37:67:weaver-inc-biz-service/src/main/java/com/weaver/inc/book/service/AbstractOpenApiService.java
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;
}
三个泛型参数 <V, S, R> 分别代表:请求体类型、服务类型、响应类型。两个函数参数的分工:
Function<MakeInvoiceCompanyInfo, S> serviceResolver:根据公司信息决定用哪个服务实例(可能是百望、可能是航信、可能是乐企)。BiFunction<S, InvoiceParams<V>, WeaResult<R>> handler:拿到服务实例后,执行具体的业务操作。
这种设计把"选哪个服务"和"用服务做什么"彻底解耦。调用方类似:
java
return handleRequest(request, companyInfoService,
companyInfo -> instanceFetcher, // 第一步:选服务
(fetcher, params) -> fetcher.makeInvoice(params) // 第二步:调方法
);
而框架层的 handleRequest 负责了所有调用方不想关心的事:日志、租户上下文设置、异常捕获、上下文清理。
九、Optional 链的工程边界
Optional 链好用,但有边界。先看一个好的例子------清晰的逐层解包:
187:193:weaver-inc-biz-service/src/main/java/com/weaver/inc/book/manager/business/adapter/engine/InstanceFetcher.java
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() 都是一个明确的属性访问,读起来像导航路径:req → companyInfo → thirdType → invoiceType。
再看一个巧妙的带 flatMap 的 Optional 链:
150:159:weaver-inc-biz-service/src/main/java/com/weaver/inc/book/service/impl/CreditMessageServiceImpl.java
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 把原始数据转成 UserEntity。三个判断条件任一不满足,整条链短路返回空列表。 这比三层嵌套 if 紧凑得多。
但 Optional 也有滥用的时候。代码库里有这样的写法:
java
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(),每层都在创建空对象作为 fallback 然后立刻调方法。这不叫函数式,这叫折磨。更清晰的写法是:
java
Integer type = Optional.ofNullable(invoice_info.getCommInfo())
.map(InvoiceCommInfo::getPro)
.map(InvoiceProVo::getType)
.orElse(0);
经验法则:Optional 链中如果出现了 orElse(new Xxx()).getYyy() 这种"创建临时对象只为链式调用"的写法,说明你应该用 .map() 替代。
十、反射 vs Function:一个反面教材
237:247:weaver-inc-biz-service/src/main/java/com/weaver/inc/book/manager/auto/AbstractAutoSupport.java
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 的对象反射调用 getReturnCode()。因为 R 没有上界约束,编译器不知道 R 有没有这个方法,只能在运行时用反射碰运气。
改法很直接------加一个 Function 参数:
java
protected <R> boolean canApplyNext(R data, Function<R, String> codeExtractor) {
return Optional.ofNullable(data)
.map(codeExtractor)
.filter("00"::equals)
.isPresent();
}
// 调用
canApplyNext(result, BWBaseRes::getReturnCode);
反射调用一次大约 50~100ns,方法引用调用大约 5ns,差一个数量级。但性能不是重点------重点是编译期安全 。反射版本在方法名打错(比如 getReturncode vs getReturnCode)时只会在运行时 NPE,而 Function 版本会在编译时就报错。
总结:Function 高级用法的几条原则
1. 柯里化不是炫技,是分层抽象的工具。 当你的框架需要"两步定位"(先选服务再选方法),Function<A, Function<B, R>> 比任何设计模式都简洁。
2. Map<Enum, Supplier> 比 switch-case 优越的地方不在于"消除了 switch",而在于实现了延迟求值。 只有命中的分支才会执行计算。
3. 函数装饰器是轻量级 AOP。 doInvokeMethod(params, key, actualMethod) 这种写法让横切关注点(日志、状态、异常)透明地叠加在业务函数上,且调用方完全可见、可控。
4. Supplier + 资源管理(锁、连接、事务)是 try-with-resources 的泛化版本。 把"需要被保护的代码"包成 Supplier,在外层统一处理获取-释放-异常。
5. Function<RawData, T> 做数据适配器,让一个读取方法服务于 N 种目标类型。 这是泛型和函数式结合最自然的场景。
6. 常量函数 x -> defaultValue 作为 getOrDefault 的 fallback,比 null 判断安全。 它保证了返回值永远是合法的函数,调用方不需要做空检查。
7. Optional 链的正确用法是 .map().map().orElse(),不是 .orElse(new Xxx()).getYyy()。 后者本质上是在滥用 Optional 来模拟空安全运算符,创建了一堆无意义的临时对象。