LambdaConversionException с дженериками: ошибка JVM?

У меня есть код с ссылкой на метод, который компилируется отлично и не работает во время выполнения.

Исключение составляет:

Caused by: java.lang.invoke.LambdaConversionException: Invalid receiver type class redacted.BasicEntity; not a subtype of implementation type interface redacted.HasImagesEntity at java.lang.invoke.AbstractValidatingLambdaMetafactory.validateMetafactoryArgs(AbstractValidatingLambdaMetafactory.java:233) at java.lang.invoke.LambdaMetafactory.metafactory(LambdaMetafactory.java:303) at java.lang.invoke.CallSite.makeSite(CallSite.java:289) 

Класс выглядит так:

 class ImageController { void doTheThing(E entity) { Set filenames = entity.getImages().keySet().stream() .map(entity::filename) .collect(Collectors.toSet()); } } 

Исключение вызывает попытку разрешения entity :: filename. filename () объявляется в HasImagesEntity. Рядом, насколько я могу судить, я получаю исключение, потому что стирание E – это BasicEntity, а JVM не (не может?) Рассматривает другие границы на E.

Когда я переписываю ссылку на метод как тривиальную лямбду, все в порядке. Мне кажется очень подозрительным, что одна конструкция работает так, как ожидалось, и ее семантический эквивалент взрывается. Может ли это быть в спецификации? Я очень стараюсь найти способ, чтобы это не было проблемой в компиляторе или во время выполнения, и ничего не придумал.

4 Solutions collect form web for “LambdaConversionException с дженериками: ошибка JVM?”

Вот упрощенный пример, который воспроизводит проблему и использует только основные classы Java:

 public static void main(String[] argv) { System.out.println(dummy("foo")); } static  int dummy(T value) { return Optional.ofNullable(value).map(CharSequence::length).orElse(0); } 

Ваше предположение верно, JRE-специфическая реализация получает целевой метод как MethodHandle который не имеет информации об общих типах. Поэтому единственное, что он видит, это то, что исходные типы не совпадают.

Как и в случае с множеством общих конструкций, на уровне байтового кода требуется тип, который не отображается в исходном коде. Поскольку LambdaMetafactory явно требует дескриптора прямого метода, ссылка на метод, которая инкапсулирует такой тип, не может быть передана в качестве MethodHandle на заводе.

Существует два возможных способа борьбы с этим.

Первым решением было бы изменить LambdaMetafactory чтобы доверять MethodHandle если тип приемника является interface и вставляет требуемый тип, который выполняется сам по себе в сгенерированном classе lambda, вместо того, чтобы отклонять его. В конце концов, он уже аналогичен параметрам и типам возврата.

В качестве альтернативы, компилятор будет отвечать за создание синтетического вспомогательного метода, инкапсулирующего вызов типа и метода, как если бы вы написали lambda-выражение. Это не уникальная ситуация. Если вы используете ссылку метода на метод varargs или создание массива вроде, например String[]::new , они не могут быть выражены как прямые обработчики методов и в итоге используются в синтетических вспомогательных методах.

В любом случае мы можем считать текущее поведение ошибкой. Но, очевидно, разработчики компилятора и JRE должны договориться о том, каким образом он должен быть обработан, прежде чем мы сможем сказать, на какой стороне находится ошибка.

Я только что исправил эту проблему в JDK9 и JDK8u45. Посмотрите эту ошибку . Для перехода к продвинутым assemblyм потребуется немного времени. Дэн просто указал мне на этот вопрос StackOverflow, поэтому я добавляю эту заметку. Когда вы найдете ошибки, пожалуйста, отправьте их.

Я обратился к этому с помощью того, что компилятор создает мост, а также подход для многих случаев сложных ссылок на методы. Мы также изучаем технические последствия.

Эта ошибка не полностью исправлена. Я просто столкнулся с LambdaConversionException в 1.8.0_72 и увидел, что в системе отслеживания ошибок Oracle есть отчеты об ошибках: link1 , link2 .

(Edit: Связанные ошибки, как сообщается, закрыты в JDK 9 b93)

В качестве простого обходного пути я избегаю обработки методов. Поэтому вместо

 .map(entity::filename) 

я делаю

 .map(entity -> entity.filename()) 

Вот код для воспроизведения проблемы на Debian 3.11.8-1 x86_64.

 import java.awt.Component; import java.util.Collection; import java.util.Collections; public class MethodHandleTest { public static void main(String... args) { new MethodHandleTest().run(); } private void run() { ComponentWithSomeMethod myComp = new ComponentWithSomeMethod(); new Caller().callSomeMethod(Collections.singletonList(myComp)); } private interface HasSomeMethod { void someMethod(); } static class ComponentWithSomeMethod extends Component implements HasSomeMethod { @Override public void someMethod() { System.out.println("Some method"); } } class Caller { public void callSomeMethod(Collection components) { components.forEach(HasSomeMethod::someMethod); // < -- crashes // components.forEach(comp -> comp.someMethod()); < -- works fine } } } 

Я нашел обходное решение для этого – это замена порядка дженериков. Например, используйте class A где вам нужно получить доступ к методу B или использовать class A если вам нужно получить доступ к методу C Конечно, если вам нужен доступ к методам обоих classов, это не сработает. Я нашел это полезным, когда один из интерфейсов был интерфейсом маркера, таким как Serializable .

Что касается исправления этого в JDK, единственной информацией, которую я смог найти, были некоторые ошибки в трекер-буфере openjdk, которые отмечены как разрешенные в версии 9, что довольно бесполезно.

  • Получение всех типов, реализующих интерфейс
  • Самый простой и аккуратный c ++ 11 ScopeGuard
  • Java Lambda Stream Distinct () на произвольном ключе?
  • Является ли lambda-выражение созданием объекта в куче каждый раз, когда оно выполняется?
  • Как преобразовать lambda в std :: функцию с помощью шаблонов
  • Почему вы используете Expression <Func >, а не Func ?
  • Слабая модель обработчика событий для использования с lambdas
  • Как захватить unique_ptr в выражение lambda?
  • Перерыв или возврат из streamа Java 8 для каждого?
  • Почему плохо использовать переменную итерации в выражении lambda
  • преобразование .net Func в .net Expression <Func >
  • Interesting Posts

    Недостаточно места на диске для обновления до Windows 10

    Что такое Инвариантная культура?

    Регулярное выражение для пропуска символа в группе захвата

    Как я могу убедить IE просто отображать приложение / json, а не предлагать его загрузить?

    Заполните поля в веб-просмотре автоматически

    Перемещение swap-файлов в выделенный раздел в Snow Leopard

    Заменить пакет приложения на Windows 10

    Выберите копию и вставьте в терминал mac или ubuntu без использования мыши

    Как подключить домашние компьютеры (Linux) от офисного компьютера (windows) с помощью шпатлевки

    Обнаруживать пользователя, вставляя строку или столбец в электронную таблицу google и реагируя в скрипте

    Как проверить наличие интернет-соединения в java?

    Преобразование JSON в XML в Java

    Предложения по совместному использованию и использованию данных между Ubuntu и Windows 7 с двойной загрузкой

    Должен ли я использовать общедоступные свойства и частные поля или публичные поля для данных?

    Что такое Generics в Java?

    Давайте будем гением компьютера.