You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java中如何在不通过委托的情况下将SFunction类型Lambda转换为TypedPropertyGetter类型?

Java中如何在不通过委托的情况下将SFunction类型Lambda转换为TypedPropertyGetter类型?

我完全理解你的困扰——用MyBatis-Plus封装抽象类简化查询时,卡在了Lambda类型转换上,还被PropertyUtils的“call site specific”异常搞头大。这个问题的核心其实是:你用lambda::apply做委托转换时,生成的新Lambda会捕获原来的SFunction实例,导致它的类里多了一个成员字段,刚好踩中了PropertyUtils的检测逻辑。

下面给你一个可行的解决方案,不用委托就能完成类型转换:

解决方案:用LambdaMetafactory直接生成目标类型Lambda

因为SFunction<T,R>的apply(T)方法和TypedPropertyGetter<T,R>的get(T)方法签名完全一致(都是接收T返回R),我们可以借助Java的LambdaMetafactory直接把SFunction的方法引用转换成TypedPropertyGetter实例,绕开中间的委托包装,这样生成的Lambda不会有任何捕获的变量,就能通过PropertyUtils的检测了。

先写一个工具类来做转换:

import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.function.Function;

import de.cronn.reflection.util.TypedPropertyGetter;

public class LambdaConverter {
    @SuppressWarnings("unchecked")
    public static <T, R> TypedPropertyGetter<T, R> convertToTypedPropertyGetter(Function<T, R> function) {
        try {
            MethodHandles.Lookup lookup = MethodHandles.lookup();
            // 定义SFunction.apply方法的方法类型
            MethodType applyMethodType = MethodType.methodType(Object.class, Object.class);
            // 定义TypedPropertyGetter.get方法的方法类型
            MethodType getMethodType = MethodType.methodType(Object.class, Object.class);
            
            // 获取SFunction实例的apply方法句柄
            MethodHandle applyHandle = lookup.unreflect(function.getClass().getMethod("apply", Object.class));
            
            // 通过LambdaMetafactory直接生成TypedPropertyGetter实例
            return (TypedPropertyGetter<T, R>) LambdaMetafactory.metafactory(
                    lookup,
                    "get", // 目标接口的方法名
                    MethodType.methodType(TypedPropertyGetter.class), // 生成的Lambda类型
                    getMethodType, // 目标方法的类型(擦除后)
                    applyHandle, // 实际调用的方法句柄
                    applyMethodType // 实际方法的类型(擦除后)
            ).getTarget().invokeExact();
        } catch (Throwable e) {
            throw new RuntimeException("转换SFunction到TypedPropertyGetter失败", e);
        }
    }
}

然后修改你抽象类里的reflectPropertyName方法,用这个工具类来转换:

private String reflectPropertyName(SFunction<T, ?> getXxxSFunc) {
    if (getXxxSFunc == null) return null;
    try {
        TypedPropertyGetter<T, ?> getter = LambdaConverter.convertToTypedPropertyGetter(getXxxSFunc);
        return PropertyUtils.getPropertyName(entityClass, getter);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

为什么这个方法能解决问题?

你之前用lambda::apply的时候,相当于创建了一个新的Lambda实例,这个实例内部会捕获原来的SFunction对象,所以它的类会自动生成一个成员字段来保存这个捕获的对象——这就是PropertyUtils检测到declaredFields.length > 0的原因。

而用LambdaMetafactory生成的Lambda,是直接绑定到原来的方法引用(比如User::getUsername),没有捕获任何外部变量,所以它的类里不会有额外的成员字段,自然能通过PropertyUtils的assertHasNoDeclaredFields检测。

备选方案:直接修改抽象方法参数类型

如果你不想引入LambdaMetafactory的代码,也可以把抽象类里的getEntityByUniqueColumn方法的参数从SFunction改成TypedPropertyGetter,这样上层调用的时候直接传User::getUsername就行:

// 抽象service修改后的方法
public <R> T getEntityByUniqueColumn(TypedPropertyGetter<T, R> lambda, R value) {
    // ... 原有逻辑,直接用lambda获取属性名
}

// 实际service调用
public Long getUid(String username) {
    return getEntityByUniqueColumn(User::getUsername, username);
}

不过这个方案需要修改你项目中所有相关的抽象方法和调用处,如果你已经有大量代码基于SFunction,那还是第一个方案更省心。

备注:内容来源于stack exchange,提问作者Emiya Elien

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.22 11:53:00