Java Function 高级使用技巧:从工程实战中来

前言

来自企业级的一个经验总结,每个技巧都经过生产环境验证,附带完整上下文。


一、柯里化:函数返回函数的实战价值

柯里化(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, ?> 参数让这个方法可以处理 CompanyConfigCustomerConfigUserConfig 三种完全不同的类型,调用方只需要传对应的方法引用。一个私有方法干了本来需要三个方法或者一堆 instanceof 才能干的事。


三、ImmutableMap + BiConsumer:服务路由表

当系统有 9 个不同的自动化服务需要根据能力码分发时,if-elseswitch 会变成灾难。看这个做法:

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))
                            )
                    );

有三个细节值得关注:

  1. 闭包捕获 。每个 BiConsumer<MakeInvoiceCompanyInfo, MakeInvoiceCompanyInfo> 的 lambda 体内都引用了外部的 authorCodeinterKey。这两个变量是"固定参数"(对于本次调用而言),而 a, b 是"动态参数"。通过闭包,你自然地实现了部分应用(Partial Application) ------把四参方法变成了双参的 BiConsumer

  2. serviceMap::containsKey 作为 filter 的谓词。这比 scene -> serviceMap.containsKey(scene) 更简洁,而且语义更清晰------"过滤出路由表中存在的场景"。

  3. 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 来模拟空安全运算符,创建了一堆无意义的临时对象。

相关推荐
三佛科技-187366133972 小时前
LP3783A芯茂微5V2.1A低功耗原边反馈充电器芯片替代PL3378/C
c语言·开发语言
不知名。。。。。。。。2 小时前
仿muduo库实现高并发服务器----EventLoop与线程整合起来
java·开发语言·jvm
编程大师哥2 小时前
JAVA 集合框架进阶
java·开发语言
TechFind2 小时前
AI Agent 开发完整教程:从零到上线的实战指南
java·javascript
xixihaha13242 小时前
使用Flask快速搭建轻量级Web应用
jvm·数据库·python
春日见2 小时前
车载系统中的CPU与内存监管
java·开发语言·驱动开发·docker·计算机外设
用户2565676133462 小时前
Android Input 系统事件分发机制深度解析
java
超越自我肖2 小时前
python--while循环的基础案例
python
2501_921649492 小时前
免费港股实时行情 API:功能、性能与接入指南
开发语言·后端·python·金融·restful