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, что довольно бесполезно.

  • Какова эффективность и производительность выражения LINQ и Lambda в .Net?
  • Несколько заказов с помощью LINQ
  • Java bean-методы с LambdaMetafactory
  • Lambdas: локальные переменные нужны final, переменные экземпляра не
  • Почему java.util.Collection не реализует новый интерфейс Stream?
  • Как назначить Func условно между lambdaми, используя условный тернарный оператор?
  • Список OrderBy Алфавитный порядок
  • Указатели функций, Закрытие и Лямбда
  • Вывод типа отражения на Java 8 Lambdas
  • Почему я не могу захватить эту ссылку ('& this') в lambda?
  • Java 8 привязанный метод ссылки?
  • Interesting Posts

    Поворот растрового изображения с использованием JNI & NDK

    Условная переменная против семафора

    Excel CSV – формат ячейки

    Почему некоторые приложения Chrome устанавливают себя как расширения?

    MathML и Java

    Сценарий Bash: тест для пустого каталога

    Почему дополнительная константа автоматически не имеет значения по умолчанию nil

    Генератор изображений BarCode в Java

    Две клавиатуры на одном компьютере. Когда я пишу с AI, хочу иметь раскладку клавиатуры в США, когда я использую BI, хочу шведский. Возможное?

    Делайте с использованием утверждений и ожидайте ключевых слов, играющих красиво в c #

    Шаблоны совпадений обновляют выходной файл без разбора по желанию

    Сравните равенство между двумя объектами в NUnit

    Внутренний class может получить доступ, но не обновлять значения – AsyncTask

    Удаление определенного URL-адреса в Chrome

    Рекомендуемый способ получить имя хоста в Java

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