Given:
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.function.Function;
class Testcase
{
@FunctionalInterface
public interface MyBuilder1<R>
{
R apply(String message);
}
@FunctionalInterface
public interface MyBuilder2<R>
{
R apply(Object message);
}
public static void main(String[] args) throws Throwable
{
Class<?> clazz = IllegalArgumentException.class;
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle mh = lookup.findConstructor(clazz, MethodType.methodType(void.class, String.class));
MethodHandle myFunctionConstructor = LambdaMetafactory.metafactory(
lookup,
"apply",
MethodType.methodType(Function.class),
mh.type().erase(),
mh,
mh.type()
).getTarget();
MethodHandle myBuilderConstructor1 = LambdaMetafactory.metafactory(
lookup,
"apply",
MethodType.methodType(MyBuilder1.class),
mh.type().erase(),
mh,
mh.type()
).getTarget();
MethodHandle myBuilderConstructor2 = LambdaMetafactory.metafactory(
lookup,
"apply",
MethodType.methodType(MyBuilder2.class),
mh.type().erase(),
mh,
mh.type()
).getTarget();
@SuppressWarnings("unchecked")
Function<String, IllegalArgumentException> functionFactory =
(Function<String, IllegalArgumentException>) myFunctionConstructor.invokeExact();
@SuppressWarnings("unchecked")
MyBuilder1<IllegalArgumentException> myBuilder1Factory =
(MyBuilder1<IllegalArgumentException>) myBuilderConstructor1.invokeExact();
@SuppressWarnings("unchecked")
MyBuilder2<IllegalArgumentException> myBuilder2Factory =
(MyBuilder2<IllegalArgumentException>) myBuilderConstructor2.invokeExact();
IllegalArgumentException runFunction = functionFactory.apply("test");
// IllegalArgumentException runBuilder1 = myBuilder1Factory.apply("test");
IllegalArgumentException runBuilder2 = myBuilder2Factory.apply("test");
}
}
Why do runFunction and runBuilder2 work while runBuilder1 throws the following exception?
java.lang.AbstractMethodError: Receiver class Testcase$$Lambda$233/0x0000000800d21d88 does not define or inherit an implementation of the resolved method 'abstract java.lang.Object apply(java.lang.String)' of interface MyBuilder1.
Given that the IllegalArgumentException constructor takes a String parameter, not an Object, shouldn't the JVM accept runBuilder1 and complain about the parameter type of the other two?
Your
MyBuilder1<R>has a functional methodwhose erased type is
In other words, unlike
FunctionorMyBuilder2, the erased parameter type isString, rather thanObject. Theerase()method ofMethodTypejust replaces all reference types withObject, which was handy forFunctionandMyBuilder2but is not suitable forMyBuilder1anymore. There is no similarly simple method for non-trivial types. You have to include type transformation code specifically for your case (unless you want to lookup the interface method via Reflection).For example, we can just change the return type to
Objectand keep the parameter types:Regarding your last question, the erased type is the type to implement, whereas the last parameter to
metafactorydetermines the intended type, i.e. derived from the Generic interface type. The generated code may have type casts from the erased type to this type when necessary. Since this type matches the constructor signature in all cases, all variants can invoke the constructor.