Java 8: converting lambda to method instance with closing enabled

(It’s hard to do a search because all results are associated with a “method reference”)

I want to get a Method instance for a lambda expression for use with an obsolete reflection-based API. The keyboard must be on, so calling thatMethod.invoke(null, ...) should have the same effect as calling lambda.

I looked at MethodHandles.Lookup , but it seems to be related to the inverse transformation. But I suggest that the bind method can help enable clousure?

Edit:

Let's say I have lambda experssion:

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

and I have an outdated structure ( SpEL ) that has an API like

 registerFunction(String name, Method method) 

which will call this Method without this argument (i.e. the method is considered static). Therefore, I need to get a special instance of Method , which includes lambda logic + clousure data.

+11
source share
2 answers

If you don’t find an elegant way, here is an ugly way ( Ideone ). Normal warning on reflection: may be violated in future releases, etc.

 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); } } 
+10
source

Well, lambda expressions are allocated to methods at compile time and until they capture this (don't have access to non- static elements), these methods will be static . The tricky part is moving on to these methods, since there is no controlled connection between the instance of the functional interface and its target method.

To illustrate this, here is the simplest case:

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

This works smoothly since there is only one lambda expression and therefore one candidate method. The name of this method is compiler specific and may contain some serial numbers or hash codes, etc.

In kludge, you need to make a serializable lambda expression and check its serialized form:

 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))); } 

It works, however, serializable lambdas come at a high price.


The simplest solution would be to add an annotation to the parameter of the lambda expression, which will be found during iteration by methods, however, at present, javac does not save the annotation properly, see also this question about this topic.


But you can also consider creating regular static methods that contain code instead of a lambda expression. Getting the Method object for a method is straightforward, and you can still create an instance of the functional interface from them using method references ...

+5
source

All Articles