When creating a lambda manually using MethodHandles.Lookup
, MethodHandle
s, MethodType
s, etc, how might one implement variable capture?
For example, with no capture:
public IntSupplier foo() {
return this::fortyTwo;
}
/**
* Would not normally be virtual, but oh well.
*/
public int fortyTwo() {
return 42;
}
and its clunkier form, using stuff in java.lang.invoke
:
public IntSupplier foo() {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType methodType = MethodType.methodType(int.class),
lambdaType = MethodType.methodType(IntSupplier.class);
MethodHandle methodHandle = lookup.findVirtual(getClass(), "fortyTwo", methodType);
CallSite callSite = LambdaMetafactory.metafactory(lookup, "getAsInt", lambdaType, methodType, methodHandle, methodType);
return (IntSupplier) callSite.getTarget().invokeExact();
}
/**
* Would not normally be virtual, but oh well.
*/
public int fortyTwo() {
return 42;
}
would return a simple, pointless IntSupplier
that returns 42
when invoked, but what if one would like to capture something?
The third argument to the bootstrap method, which you named
lambdaType
, is the invoked type of the associatedinvokedynamic
instruction (normally filled in by the JVM). It’s semantic is defined by the bootstrap method and in the case of theLambdaMetaFactory
, it specifies the functional interface as return type (the type of the object to construct) and the values to capture as parameter type (the type of the values to consume when constructing a lambda instance).So in order to capture
this
, you have to add the type ofthis
to your invoked type and passthis
as an argument to theinvokeExact
call:If you want to capture more values, you have to add them to the signature in the right order. E.g., to capture another
int
value:The target method will have a signature consisting of the
this
type, if notstatic
, followed by all parameter types. The capture values will map in order to this signature’s types from left to right and the remaining parameter types, if any, contribute to the functional signature, hence have to match theinterface
method’s parameter types.This implies that when there are no captured values and the target method is not
static
, the method receiver type might become associated with the first type of the functional signature, as inToIntFunction<String> f=String::length;
.Your code will not run. Since your
fortyTwo
method is not static, you would have to capturethis
by usingMethodType.methodType(IntSupplier.class, getClass())
as the 3rd argument tometafactory
and then passingthis
as an argument toinvokeExact
.Here's an example of capturing a string using a static method to keep things simpler: