For example, when I execute the following:
public static int addOne(Number base) {
return base.intValue() + 1;
}
public static interface AddOneLambda {
public int addOne(Integer base);
}
public static void main(String[] a) throws Throwable {
Method lambdaMethod = AddOneLambda.class.getMethod("addOne", Integer.class);
Class<?>[] lambdaParameters = Stream.of(lambdaMethod.getParameters()).map(p -> p.getType()).toArray(Class[]::new);
Method sourceMethod = Main.class.getMethod("addOne", Number.class);
Class<?>[] sourceParameters = Stream.of(sourceMethod.getParameters()).map(p -> p.getType()).toArray(Class[]::new);
MethodHandles.Lookup lookup = MethodHandles.lookup();
CallSite site = LambdaMetafactory.metafactory(lookup, //
lambdaMethod.getName(), //
MethodType.methodType(lambdaMethod.getDeclaringClass()), //
MethodType.methodType(lambdaMethod.getReturnType(), lambdaParameters), //
lookup.unreflect(sourceMethod), //
MethodType.methodType(sourceMethod.getReturnType(), sourceParameters));
AddOneLambda addOneLambda = (AddOneLambda) site.getTarget().invoke();
System.out.println("1 + 1 = " + addOneLambda.addOne(1));
}
I receive the following exception from metafactory:
LambdaConversionException: Type mismatch for dynamic parameter 0: class java.lang.Number is not a subtype of class java.lang.Integer
I don't understand this. Passing an Integer to the AddOneLambda should always be fine, because the underlying addOne method can accept Integers as part of it's Number signature - so I believe this configuration should be "safe".
On the other hand, when I execute the above with this change:
public static int addOne(Integer base) {
return base.intValue() + 1;
}
public interface AddOneLambda {
public int addOne(Number base);
}
The metafactory allows now this without exception, but it doesn't seem right. I can pass any kind of Number to AddOneLambda, even though the underlying method can only handle Integers - so I believe this configuration to be "unsafe". Indeed, if I now call addOneLambda.addOne(1.5)
I receive an exception for inability to cast Double to Integer.
Why then is my initial code not allowed, while the change which ultimately allows for invalid types to be passed ok? Is it something to do with the values I'm passing to metafactory, is metafactory incorrectly checking the types, or does this prevent some other kind of situation I haven't considered? If relevant, I'm using JDK 17.0.3.
You seem to have misunderstood the purpose of the
dynamicMethodType
argument ofmetafactory
(the sixth one, which you have asMethodType.methodType(sourceMethod.getReturnType(), sourceParameters)
). The point of it is to produce runtime type errors: the generated method will have the type given byinterfaceMethodType
(fourth parameter) but will check that its parameters and return value obey thedynamicMethodType
. For example, after erasure,Consumer
containsvoid accept(Object)
, and if you do something like(Consumer<String>)String::intern
the generatedaccept(Object)
must do a checked cast toString
. The underlying call tometafactory
will pass the method typevoid(Object)
asinterfaceMethodType
andvoid(String)
asdynamicMethodType
.As the purpose of
dynamicMethodType
is to act as "further" restrictions oninterfaceMethodType
, it is considered an error for it to be looser. From a theoretical/user standpoint this seems a bit ugly, but from an implementation standpoint (why generate a useless cast? does producing a wider type indicate a bug in the caller?) you might consider it justified.To get all language lawyery, you have violated the "linkage invariants" in
LambdaMetafactory
's documentationAs you are not dealing with generics or anything similarly interesting, you should do as the Javadoc for
metafactory
suggests and pass the sameint(Integer)
type forinterfaceMethodType
anddynamicMethodType
.