mercredi 10 mai 2023

How to convert lambda expression from a type to another type in Java?

I'm using mybatis-plus's lambda chained query and it looks like this

Long uid = new LambdaQueryChainWrapper(baseMapeer)
            .select(User::getUid)
            .eq(User::getUsername, username)
            .get();

I think it's so tedious that I have to write every class, and sometimes I needed to add caching operations, so I write an abstract class to simplify the operation, like this

// abstract service
public <R> T getEntityByUniqueColumn(SFunction<T, R> lambda, R value) {
    // use mybatis-plus util
    final String keyColumn = keyName();
    // USE THIRD-PARTY LIBRARY REFLECTION UTIL
    final String propertyName = reflectProperty(lambda);
    // use mybatis-plus util
    final String columnName = reflectColumn(propertyName);

    return new QueryChainWrapper(baseMapper)
             .select(keyColumn)
             .eq(columnName, value)
             .get();
}

// actual service
public Long getUid(String username) {
    return getEntityByUniqueColumn(User::getUsername, username);
}

I'm using the 'de.cronn.reflection.util.PropertyUtils' method to get propertyName through get lambda function

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

Now the problem arises. PropertyUtils requires a lambda expression with a TypedPropertyGetter parameter, while my abstract method provides a lambda expression with an SFunction parameter.

The method signature of the former is V get(T t), and the method signature of the latter is V apply(T t). I tried to convert from SFunction to TypedPropertyGetter by using lambda::apply.

But I encountered an exception when I executed it. The reason is: PropertyUtils will eventually check whether the lambda method passed is call site specific. If it is, it throws an exception. This means that we cannot express lambda expressions by delegation (there are attributes inside), and we must pass this parameter by user-defined, such as User::getUsername.

// source-code
private static void assertHasNoDeclaredFields(Object lambda) {
    if (hasDeclaredFields(lambda)) {
        throw new IllegalArgumentException(lambda + " is call site specific");
    }
}

private static boolean hasDeclaredFields(Object lambda) {
    return lambda.getClass().getDeclaredFields().length > 0;
}

There are many methods of abstract classes in my project. I want to know how to convert this lambda expression into a zero-one type without delegation.





Aucun commentaire:

Enregistrer un commentaire