4

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.

CraftWeaver
  • 707
  • 1
  • 8
  • 21

1 Answers1

3

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.

Luke Woodward
  • 63,336
  • 16
  • 89
  • 104
  • Thanks. You nailed my misunderstanding exactly. If you happen to know of any documentation/tutorials that describe different ways to use LambdaMetafactory, other than the [often cryptic] API doc, please post. It would be very helpful. – CraftWeaver May 30 '15 at 14:09
  • @CraftWeaver: If you got the `.generic()` call from [here](http://stackoverflow.com/a/27605965/2711488), I have to explain that it serves well for generic functional interfaces like [`Function`](http://docs.oracle.com/javase/8/docs/api/java/util/function/Function.html) which have a raw signature where all parameter and return types are `Object`, but nothing else. So it came handy there, and it worked, had you used `Function,Object>` as functional interface, but as soon as primitive types or other bounds than `Object` come into play, it is inappropriate. – Holger Jul 13 '15 at 18:31