2016-01-21 65 views
6

(这是很难搜索,因为结果都是关于“方法参考”)的Java 8:转换拉姆达与clousure方法实例包括

我希望得到一个Method实例与一个使用lambda表达式传统的基于反射的API。应该包含clousure,所以调用thatMethod.invoke(null, ...)应该与调用lambda具有相同的效果。

我已经看过MethodHandles.Lookup,但它似乎只与逆向变换有关。但我认为bind方法可能有助于包括clousure?

编辑:

说我有上午的λ表达研究:

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

,我有一个旧的框架(SpEL),有一个API类似

registerFunction(String name, Method method) 

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

+0

没有什么像lambda表达式的'方法'实例。 Lambda是匿名函数的语法糖。他们为你提供了一个实例,但是你不能从中得到一个方法 – Jatin

+0

@Jatin显然,合成的函数接口驱动仍然只是一个正常的方法对象,你可以使用正常的反射来访问。我只是想知道如何弄好包裹。 –

回答

7

如果你没有找到一个优雅的方式,这里是丑陋的方式(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); 
    } 
} 
+3

我向你致敬! –

+0

我在'MethodHandle'属性中查找了可能性,看起来它能做的最好的事情是生成实例方法,因此由于动态需求而成为死胡同。 –

+1

最有趣的部分是你让'm'可访问的方式;不是调用'setAccessible(true)',而是获取后端字段'override'来调用'Field'上的'setAccessible(true)'以后使用它将其设置为'true'。我想,如果你经常使用'setAccessible(true)',那会发生什么...... – Holger

4

好,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; 
      } 
    } 
} 

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

在组装机是使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不存储标注正确,也看到this question这个话题。


但你也可以考虑只创建普通static方法持代码,而不是一个lambda表达式。为一个方法获得一个Method对象是直接的,你仍然可以使用方法引用创建一个功能接口实例...