当前位置: 首页 > 知识库问答 >
问题:

Java 8:将lambda转换为包含闭包的方法实例

柯景龙
2023-03-14

(这很难搜索,因为结果都是关于“方法参考”)

我想为lambda表达式获取一个method实例,用于传统的基于反射的API。应该包括clousure,因此调用that Method.invoke(null,...)应该与调用lambda具有相同的效果。

我已经研究了MethodHandles。查找,但它似乎只与反向转换相关。但是我想bind方法可能有助于包含clousure?

编辑:

假设我是lambda Expersion:

Function<String, String> sayHello = name -> "Hello, " + name;

我有一个遗留框架(SpEL),它有一个类似API的

registerFunction(String name, Method method)

它将调用给定的方法,而不使用此参数(即假设为静态的方法)。所以我需要得到一个特殊的方法实例,它包括lambda逻辑和clousure数据。

共有3个答案

樊博雅
2023-03-14

由于问题特别提到了SpEL(我在使用SpEL时也发现了这个问题),在不使用方法引用的情况下向评估上下文添加自定义函数的另一种方法是向标准评估上下文添加自定义方法分解器(javadoc,GitHub)。这种方法的一个优点是,可以使用它将静态和非静态方法添加到评估上下文中,其中只能使用registerFunction方法添加静态方法。

将自定义omeodResolver添加到标准评估上下文的代码相当简单。下面是一个显示如何执行的可执行示例

public static void main(String[] args) throws Exception {
    Function<String, String> sayHello = name -> "Hello, " + name;

    // The evaluation context must have a root object, which can be set in the StandardEvaluationContext
    // constructor or in the getValue method of the Expression class. Without a root object, the custom
    // MethodResolver will not be called to resolve the function.
    Object                    rootObject                = new Object();
    StandardEvaluationContext standardEvaluationContext = new StandardEvaluationContext(rootObject);

    // Add the custom MethodResolver to the evaluation context that will return a MethodExecutor that
    // Spring can use to execute the sayHello function when an expression contains "sayHello('<any string>')".
    standardEvaluationContext.addMethodResolver((context, targetObject, methodName, argumentTypes) -> {
        MethodExecutor methodExecutor = null;

        if (methodName.equals("sayHello")
            && argumentTypes.size() == 1
            && String.class.isAssignableFrom(argumentTypes.get(0).getObjectType())
        ) {
            methodExecutor = (innerContext, target, arguments) -> {
                final String name = arguments[0].toString();
                return new TypedValue(sayHello.apply(name));
            };
        }

        return methodExecutor;
    });

    // Create an expression parser, parser the expression, and get the evaluated value of the expression.
    SpelExpressionParser expressionParser = new SpelExpressionParser();
    Expression           expression       = expressionParser.parseExpression("sayHello('World!')");
    String               expressionValue  = expression.getValue(standardEvaluationContext, String.class);

    // Output the expression value, "Hello, World!", to the console.
    System.out.println(expressionValue);
}

通过执行上述代码输出到控制台的表达式的值为:

Hello, World!

请注意,当使用MethodResolver将函数添加到求值上下文中时,该函数不应在表达式字符串中加前缀。这是使用MethodResolver与使用registerFunction向评估上下文添加函数的主要区别。

sayHello('World!')  // will work!
#sayHello('World!') // will not work!

如果您正在考虑将现有解决方案从使用registerFunction方法迁移到使用MethodResolver方法,请记住这一点。

郏志学
2023-03-14

嗯,lambda表达式在编译过程中被分解成方法,只要它们不捕获这个(不访问非静态的)成员,这些方法就会是静态的。棘手的部分是获取这些方法,因为函数接口实例与其目标方法之间没有可检查的连接。

为了说明这一点,这里是最简单的情况:

public class LambdaToMethod {
    public static void legacyCaller(Object arg, Method m) {
        System.out.println("calling Method \""+m.getName()+"\" reflectively");
        try {
            m.invoke(null, arg);
        } catch(ReflectiveOperationException ex) {
            ex.printStackTrace();
        }
    }
    public static void main(String[] args) throws URISyntaxException
    {
        Consumer<String> consumer=s -> System.out.println("lambda called with "+s);
        for(Method m: LambdaToMethod.class.getDeclaredMethods())
            if(m.isSynthetic() && m.getName().contains("lambda")) {
                legacyCaller("a string", m);
                break;
            }
    }
}

由于只有一个lambda表达式,因此只有一个候选方法,因此该方法工作顺利。该方法的名称是特定于编译器的,可能包含一些序列号或哈希代码等。

On kludge是使lambda表达式可序列化并检查其序列化形式:

static Method lambdaToMethod(Serializable lambda) {
    for(Class<?> cl=lambda.getClass(); cl!=null; cl=cl.getSuperclass()) try {
        Method m=cl.getDeclaredMethod("writeReplace");
        m.setAccessible(true);
        try {
            SerializedLambda sl=(SerializedLambda)m.invoke(lambda);
            return LambdaToMethod.class.getDeclaredMethod(sl.getImplMethodName(),
                MethodType.fromMethodDescriptorString(sl.getImplMethodSignature(),
                    LambdaToMethod.class.getClassLoader()).parameterArray());
        } catch(ReflectiveOperationException ex) {
            throw new RuntimeException(ex);
        }
    } catch(NoSuchMethodException ex){}
    throw new AssertionError();
}
public static void main(String[] args)
{
    legacyCaller("a string", lambdaToMethod((Consumer<String>&Serializable)
        s -> System.out.println("first lambda called with "+s)));
    legacyCaller("a string", lambdaToMethod((Consumer<String>&Serializable)
        s -> System.out.println("second lambda called with "+s)));
}

这是可行的,然而,可序列化的lambda价格很高。

最简单的解决方案是在遍历方法时为lambda表达式的参数添加注释,但是,目前,javac没有正确存储注释,另请参阅有关此主题的问题。

但是您也可以考虑只创建保存代码而不是lambda表达式的普通静态方法。为方法获取方法对象是直接的,您仍然可以使用方法引用从它们中创建函数接口实例...

公风史
2023-03-14

如果你没有找到一个优雅的方法,这里是丑陋的方法(想法)。涉及反射时的常见警告:可能会在未来的版本中中断等。

public static void main(String[] args) throws Exception {
  Function<String, String> sayHello = name -> "Hello, " + name;
  Method m = getMethodFromLambda(sayHello);
  registerFunction("World", m);
}

static void registerFunction(String name, Method method) throws Exception {
  String result = (String) method.invoke(null, name);
  System.out.println("result = " + result);
}

private static Method getMethodFromLambda(Function<String, String> lambda) throws Exception {
  Constructor<?> c = Method.class.getDeclaredConstructors()[0];
  c.setAccessible(true);
  Method m = (Method) c.newInstance(null, null, null, null, null, 0, 0, null, null, null, null);
  m.setAccessible(true); //sets override field to true

  //m.methodAccessor = new LambdaAccessor(...)
  Field ma = Method.class.getDeclaredField("methodAccessor");
  ma.setAccessible(true);
  ma.set(m, new LambdaAccessor(array -> lambda.apply((String) array[0])));

  return m;
}

static class LambdaAccessor implements MethodAccessor {
  private final Function<Object[], Object> lambda;
  public LambdaAccessor(Function<Object[], Object> lambda) {
    this.lambda = lambda;
  }

  @Override public Object invoke(Object o, Object[] os) {
    return lambda.apply(os);
  }
}
 类似资料:
  • 问题内容: (这很难搜索,因为结果全都与“方法参考”有关) 我想获取一个Methodlambda表达式的实例,以与基于传统反射的API一起使用。应该包括clousure,因此调用应与调用lambda具有相同的效果。 我已经看过,但是它似乎只与反向转换有关。但是我想该bind方法可能有助于包括clousure? 编辑: 假设我有lambda扩展功能: 我有一个遗留框架(SpEL),其API 这将Me

  • 本文向大家介绍groovy 将方法转换为闭包,包括了groovy 将方法转换为闭包的使用技巧和注意事项,需要的朋友参考一下 示例 可以使用&运算符将方法转换为闭包。            

  • 我从. csv文件读取数据到熊猫数据框如下。对于其中一个列,即,我想将列类型指定为。问题是系列缺少/空值。 当我在读取.csv时尝试将列强制转换为整数时,我得到: 或者,在阅读以下内容后,我尝试转换列类型,但这次我得到: 我如何处理这个问题?

  • 我有一个docx4j生成的文件,其中包含几个表格、标题,最后还有一个excel生成的曲线图。 我尝试了许多方法,以将此文件转换为PDF,但没有得到任何成功的结果。 带有xsl fo的Docx4j不起作用,docx文件中包含的大部分内容尚未实现,并以红色文本显示为“未实现” 我在Apache POI中使用的代码如下: 我不知道该怎么做才能得到PDF中的图表,有人能告诉我如何继续吗? 提前感谢。

  • 问题内容: 我从HTML页面中将一个字符串输入到Java HTTPServlet中。根据我的要求,我得到了显示汉字的ASCII码: “&#21487;&#20197;&#21578;&#35785;&#25105;” (无空格) 如何将该字符串转换为Unicode? HTML代码: Java代码: 如果我打印问题[0],则会得到以下值:“&#21487;&#20197;&#21578;&#3578

  • 我想将以下内容从perl5转换为perl6, 它创建一个包含 16 个字符的字符串,其中每个字符都有一个从 0 到 255 的随机选取值。Perl5 不会为这些字符分配任何语义,因此它们可以是字节、Unicode 代码点或其他字符。 我想我能过得去 但是我被困在使用包装上,这是我的尝试, 并且结果可以是错误, 或类似的东西, 我的思路是,打包整数列表将返回一个Buf,然后解码,这将产生所需的Str