问题
I get an exception when I call metafactory
. It says:
java.lang.invoke.LambdaConversionException:
Incorrect number of parameters for instance method
invokeVirtual my.ExecuteTest$AProcess.step_1:()Boolean;
0 captured parameters,
0 functional interface method parameters,
0 implementation parameters
I do not understand all from the documentation of LambdaMetafactory.metafactory
. I have problems figuring out the correct parameters:
- MethodHandles.Lookup caller -- thats easy
- String invokedName -- I am fairly certain here
- MethodType invokedType -- whats this?
- MethodType samMethodType -- err... not sure here
- MethodHandle implMethod -- that's fine
- MethodType instantiatedMethodType -- whats this, again? Second time?
So it boils down to what are the differences between:
- MethodType invokedType
- MethodType samMethodType
- MethodType instantiatedMethodType
My code is like this:
package my;
import java.lang.invoke.*;
import java.lang.reflect.Method;
public class Execute {
public interface ProcessBase {};
@FunctionalInterface
public interface Step {
Boolean apply();
}
public Step getMethodFromStepid(ProcessBase process, int stepid) {
try {
// standard reflection stuff
final MethodHandle unreflect = caller.unreflect(method);
final String mname = "step_"+stepid;
// new java8 method reference stuff
final Method method = process.getClass().getMethod(mname);
final MethodType type=MethodType.methodType(Boolean.class);
final MethodType stepType=MethodType.methodType(Step.class);
final MethodHandles.Lookup caller = MethodHandles.lookup();
final CallSite site = LambdaMetafactory.metafactory(
caller, "apply", stepType, type, unreflect, type); // damn
// convert site to my method reference
final MethodHandle factory = site.getTarget();
final Step step = (Step) factory.invoke();
return step;
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
}
}
with the tests
package my;
import org.junit.Test;
import static org.junit.Assert.*;
public class ExecuteTest {
private class AProcess implements Execute.ProcessBase {
public Boolean step_1() { return true; }
public Boolean step_2() { return false; }
}
@Test
public void getMethodFromStepid() throws Exception {
final AProcess process = new AProcess();
{
final Execute.Step methodRef = instance.getMethodFromStepid(process, 1);
final boolean result = methodRef.apply();
assertTrue(result);
}
{
final Execute.Step methodRef = instance.getMethodFromStepid(process, 2);
final boolean result = methodRef.apply();
assertFalse(result);
}
}
private final Execute instance = new Execute();
}
回答1:
The first three parameters are not special to lambda expressions, but standard arguments to bootstrap methods of invokedynamic
instruction. The lookup
parameter encapsulates the caller’s context, the invokedName
and invokedType
parameters represent the name and type of the invokedynamic
instruction.
It’s up to the bootstrap method to assign more semantic do it. Since in this context, the purpose of this instruction is produce a lambda expression instance, it will consume captured values and produce an interface
instance. So the invokedType
will have parameter types reflecting the type of captured values or be parameter-less for non-capturing lambdas and have a return type matching the desired functional interface. The invokedName
is used to specify the functional interface’s method name, which is unusual as it’s not actually invoked here, but since the invoked name has no other meaning otherwise, this parameter is reused here.
The samMethodType
is the signature of the functional interface’s method to implement (on the byte code level), which is identical to instantiatedMethodType
as long as, e.g. Generics is not involved. Otherwise, samMethodType
will be subject to type erasure whereas instantiatedMethodType
incorporates the actual type arguments, e.g. to implement a Function<String,Integer>
invokedType
will have a return type ofFunction
samMethodType
will be(Object)Object
instantiatedMethodType
will be(String)Integer
Note that for your specific case, the types are basically correct, but since you want to invoke the target method on the provided process
instance, you have to bind it to the lambda instance (you didn’t even try). Unfortunately, you didn’t make clear what kind of actual problem you have (i.e. that you are getting a LambdaConversionException
) in your question, so I didn’t notice the problem before.
As said above, the invokedType
must contain the types of the values to capture as parameter types. Then, you have to pass the actual process
instance to the invoke
call. As the name suggests, invokedType
must match the type of invoke
:
public Step getMethodFromStepid(ProcessBase process, int stepid) {
try {
// standard reflection stuff
final String mname = "step_"+stepid;
final Method method = process.getClass().getMethod(mname);
// new java8 method reference stuff
final MethodType type=MethodType.methodType(Boolean.class);
// invokedType: bind process, generate Step
final MethodType stepType=MethodType.methodType(Step.class,process.getClass());
final MethodHandles.Lookup caller = MethodHandles.lookup();
final MethodHandle unreflect = caller.unreflect(method);
final CallSite site = LambdaMetafactory.metafactory(
caller, "apply", stepType, type, unreflect, type);
// convert site to my method reference
final MethodHandle factory = site.getTarget();
// pass the value to bind and get the functional interface instance
final Step step = (Step)factory.invoke(process);
return step;
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
}
来源:https://stackoverflow.com/questions/39640547/types-in-a-lambdametafactory