Reputation: 707
I'm getting an AbstractMethodError
from invoking a method defined by a call to LambdaMetafactory#metafactory()
. I can't figure out what I'm doing wrong to cause it. I've looked at quite a few examples of using LambdaMetafactory#metafactory()
online, but haven't found anything that exactly matches what I'm trying to do.
Here's the [entire] output of running the attached code:
Result[0] = "version 1"
Result[0] = "1"
Exception in thread "main" java.lang.AbstractMethodError
at junk.LMTest.invokeMaker(LMTest.java:52)
at junk.LMTest.main(LMTest.java:65)
What I'm trying to do is create a class that has a single field that can be assigned a lambda directly, or be assigned by a lookup of a class name and method name. The reason for the duality is to abstract away the way in which the method being invoked was specified (either specified directly in code, or specified in a configuration file).
The attached code defines a functional interface ListMaker
with a method that produces a 1-element list from the string representation of an object. It contains a static method listify
that implements a function matching the interface's method's signature, and will be used for the set-the-method-directly portion of the sample.
Here's the code:
package junk;
import java.lang.invoke.CallSite;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class LMTest
{
@FunctionalInterface
public interface ListMaker
{
public List<String> makeList(Object obj);
}
private ListMaker maker;
public static List<String> listify(Object obj)
{
List<String> l = new ArrayList<>();
l.add(obj.toString());
return l;
}
public void setMaker(ListMaker maker)
{
this.maker = maker;
}
public void setMaker(String className, String methodName)
throws Throwable
{
Method m = Class.forName(className).getDeclaredMethod(methodName, Object.class);
MethodHandles.Lookup l = MethodHandles.lookup();
MethodHandle handle = l.unreflect(m);
CallSite cs = LambdaMetafactory.metafactory(l,
"makeList",
MethodType.methodType(ListMaker.class),
handle.type().generic(),
handle,
handle.type());
maker = (ListMaker)cs.getTarget().invoke();
}
public void invokeMaker(Object obj)
{
String result0 = maker.makeList(obj).get(0);
System.out.println("Result[0] = \"" + result0 + "\"");
}
public static void main(String[] args)
throws Throwable
{
LMTest lmt = new LMTest();
lmt.setMaker(LMTest::listify);
lmt.invokeMaker("version 1");
lmt.invokeMaker(1);
//
lmt.setMaker("junk.LMTest", "listify");
lmt.invokeMaker("version 2");
lmt.invokeMaker(2);
}
}
I've been able to understand the similar examples I've found online, but they are all end-results; I haven't been able to find anything descriptive enough (for me, at least) on how the end-results were derived to help me figure out what I'm doing wrong.
Upvotes: 4
Views: 1279
Reputation: 64959
I think the error is the use of .generic()
in handle.type().generic()
in your call to LambdaMetafactory.metafactory()
.
I took your code, removed the call to .generic()
and your code ran successfully.
The documentation for the generic()
method says that this method converts all types within the MethodType
it is called on to Object. h.type()
is a signature for a method that takes an Object and returns a List, whereas h.type().generic()
is a signature for a method that takes an Object and returns an Object.
I don't think that the generic()
method has anything to do with generic types. Please don't feel you have to use it just because the method in your functional interface has a parameter with a generic type. I admit I hadn't come across this method before, but I think it has a confusing name.
Upvotes: 3