Java 8:将lambda转换为包含clousure的Method实例

bil*_*.cn 11 reflection lambda java-8

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

我想获得一个Methodlambda表达式的实例,以便与基于遗留反射的API一起使用.应该包含clousure,因此调用thatMethod.invoke(null, ...)应该与调用lambda具有相同的效果.

我看过MethodHandles.Lookup,但它似乎只与逆向变换有关.但我想这种bind方法可能有助于包括clousure?

编辑:

说我有lambda experssion:

Function<String, String> sayHello = name -> "Hello, " + name;
Run Code Online (Sandbox Code Playgroud)

我有一个具有API 的遗留框架(SpEL)

registerFunction(String name, Method method)
Run Code Online (Sandbox Code Playgroud)

这将调用Method没有this参数的给定(即假定方法是静态的).所以我需要得到一个Method包含lambda逻辑+ clousure数据的特殊实例.

ass*_*ias 10

如果你找不到优雅的方式,这就是丑陋的方式(Ideone).涉及反思时的常见警告:未来版本可能会中断等

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 我向你致敬,感谢你们这个惊人的hackery! (3认同)

Hol*_*ger 5

好吧,lambda表达式在编译期间被分解为方法,只要它们不捕获this(不访问非static成员),这些方法将是static。棘手的部分是使用这些方法,因为在功能接口实例与其目标方法之间没有可检查的连接。

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

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;
            }
    }
}
Run Code Online (Sandbox Code Playgroud)

由于只有一个lambda表达式,因此只有一种候选方法,因此这种方法可以顺利进行。该方法的名称特定于编译器,并且可能包含一些序列号或哈希码等。

在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)));
}
Run Code Online (Sandbox Code Playgroud)

这可以工作,但是可序列化的lambda价格很高。


最简单的解决方案是在迭代方法时将注释添加到要查找的lambda表达式的参数,但是,当前,javac该注释存储不正确,另请参阅关于此主题的此问题


但是您也可以考虑只创建static保存代码的普通方法,而不是lambda表达式。Method为方法获取对象很简单,您仍然可以使用方法引用从它们中创建功能接口实例…