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

时间:2016-01-21 13:46:00

标签: reflection lambda java-8

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

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

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

编辑:

说我有lambda experssion:

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

我有一个遗留框架(SpEL),它有一个像

这样的API
registerFunction(String name, Method method)

将调用给定的Method而没有this参数(即假定为静态的方法)。因此,我需要获得一个包含lambda逻辑+ clousure数据的特殊Method实例。

2 个答案:

答案 0 :(得分: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);
  }
}

答案 1 :(得分:4)

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

为了说明这一点,这里是最简单的案例:

static

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

on kludge是使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;
            }
    }
}
然而,这是有效的,可序列化的lambdas价格很高。

最简单的解决方案是在迭代方法时为要找到的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))); } 没有正确存储注释,另请参阅{{3关于这个话题。

但您也可以考虑创建保存代码而不是lambda表达式的普通javac方法。获取方法的static对象是直截了当的,您仍然可以使用方法引用创建一个功能接口实例...