Как устранить AbstractMethodError из LambdaMetafactory

Я получаю AbstractMethodError от вызова метода, определенного вызовом LambdaMetafactory#metafactory(). Я не могу понять, что я делаю неправильно, чтобы вызвать это. Я просмотрел довольно много примеров использования LambdaMetafactory#metafactory() в Интернете, но не нашел ничего, что точно соответствовало бы тому, что я пытаюсь сделать.

Вот [полный] вывод запуска прилагаемого кода:

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)

Что я пытаюсь сделать, так это создать класс с одним полем, которому можно назначить лямбду напрямую или назначить путем поиска имени класса и имени метода. Причина двойственности заключается в том, чтобы абстрагироваться от способа, которым был указан вызываемый метод (либо указанный непосредственно в коде, либо указанный в файле конфигурации).

Прилагаемый код определяет функциональный интерфейс ListMaker с методом, создающим одноэлементный список из строкового представления объекта. Он содержит статический метод listify, который реализует функцию, соответствующую сигнатуре метода интерфейса, и будет использоваться для части примера, посвященной непосредственному заданию метода.

Вот код:

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);
  }
}

Я смог понять аналогичные примеры, которые нашел в Интернете, но все они являются конечными результатами; Я не смог найти ничего достаточно описательного (по крайней мере для меня) о том, как были получены конечные результаты, чтобы помочь мне понять, что я делаю неправильно.


person CraftWeaver    schedule 30.05.2015    source источник


Ответы (1)


Я думаю, что ошибка заключается в использовании .generic() в handle.type().generic() в вашем вызове LambdaMetafactory.metafactory().

Я взял ваш код, удалил вызов .generic(), и ваш код успешно запустился.

документация по методу generic()< /a> говорит, что этот метод преобразует все типы внутри MethodType, для которых он вызывается, в Object. h.type() — это сигнатура для метода, который принимает объект и возвращает список, тогда как h.type().generic() — это сигнатура для метода, который принимает объект и возвращает объект.

Я не думаю, что метод generic() имеет какое-либо отношение к универсальным типам. Пожалуйста, не думайте, что вам нужно использовать его только потому, что метод в вашем функциональном интерфейсе имеет параметр с общим типом. Признаюсь, раньше я не сталкивался с этим методом, но мне кажется, что у него сбивающее с толку название.

person Luke Woodward    schedule 30.05.2015
comment
Спасибо. Вы точно уловили мое непонимание. Если вам известны какие-либо документы/учебники, описывающие различные способы использования LambdaMetafactory, кроме [часто загадочного] документа по API, опубликуйте. Это было бы очень полезно. - person CraftWeaver; 30.05.2015
comment
@CraftWeaver: если вы получили вызов .generic() из здесь, я должен объяснить, что он хорошо подходит для общих функциональных интерфейсов, таких как < href="http://docs.oracle.com/javase/8/docs/api/java/util/function/Function.html" rel="nofollow noreferrer">Function с необработанной подписью, где все типы параметров и возвращаемых значений — Object, и ничего больше. Так что там это пригодилось, и это работало, если бы вы использовали Function<List<String>,Object> в качестве функционального интерфейса, но как только в игру вступают примитивные типы или другие ограничения, кроме Object, это неуместно. - person Holger; 13.07.2015