TL; DR Компилятор Eclipse генерирует сигнатуру метода для лямбда-экземпляра, который недопустим в соответствии со спецификацией. Из-за того, что в JDK 9 добавлен дополнительный код проверки типов для лучшего применения спецификации, неправильная подпись теперь вызывает исключение при работе на Java 11.
Проверено также на Eclipse 2019-03 с этим кодом:
public class Main {
public static void main(String[] args) {
getHasValue().addValueChangeListener(evt -> {});
}
public static HasValue<?, ?> getHasValue() {
return null;
}
}
interface HasValue<E extends HasValue.ValueChangeEvent<V>,V> {
public static interface ValueChangeEvent<V> {}
public static interface ValueChangeListener<E extends HasValue.ValueChangeEvent<?>> {
void valueChanged(E event);
}
void addValueChangeListener(HasValue.ValueChangeListener<? super E> listener);
}
Даже при использовании null
в качестве получателя код завершается ошибкой при загрузке с той же ошибкой.
Используя javap -v Main
, мы можем видеть, в чем проблема. Я вижу это в таблице BoostrapMethods:
BootstrapMethods:
0: #48 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#50 (Lmain/HasValue$ValueChangeEvent;)V
#53 REF_invokeStatic main/Main.lambda$0:(Ljava/lang/Object;)V
#54 (Ljava/lang/Object;)V
Обратите внимание, что последний аргумент (константа # 54) равен (Ljava/lang/Object;)V
, тогда как javac
генерирует (Lmain/HasValue$ValueChangeEvent;)V
. то есть сигнатура метода, которую Eclipse хочет использовать для лямбды, отличается от того, что javac
хочет использовать.
Если требуемая сигнатура метода является стиранием целевого метода (что, по-видимому, имеет место), то правильная сигнатура метода действительно равна (Lmain/HasValue$ValueChangeEvent;)V
, поскольку это стирание целевого метода, а именно:
void valueChanged(E event);
Где E
равно E extends HasValue.ValueChangeEvent<?>
, так что будет стерто до HasValue.ValueChangeEvent
.
Проблема, похоже, связана с ECJ, и, похоже, она была поднята на поверхность JDK-8173587 ( revision ) (К сожалению, это, кажется, частный билет.) который добавляет дополнительные проверки типов, чтобы убедиться, что тип метода SAM действительно совместим с типом экземпляра метода. В соответствии с документацией LambdaMetafactory::metafactory
конкретный тип метода должен быть таким же, либо специализация типа метода SAM:
instantiatedMethodType - тип подписи и возвращаемого значения, который должен динамически применяться во время вызова. Это может быть то же самое, что и samMethodType, или специализация.
который тип метода, сгенерированный ECJ, очевидно, не является, так что это приводит к исключению. (хотя, если честно, я не вижу нигде определенного определения, что представляет собой «специализацию» в этом случае). Я сообщил об этом в Egipse bugzilla здесь: https://bugs.eclipse.org/bugs/show_bug.cgi?id=546161
Я предполагаю, что это изменение было сделано где-то в JDK 9, так как исходный код уже был модульным на тот момент, и дата пересмотра довольно ранняя (февраль 2017 года).
Поскольку javac
генерирует правильную сигнатуру метода, вы можете переключиться на нее в качестве временного решения.