Ответ 1
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 (ревизия) (К сожалению, это похоже на частный тикет.), Который добавляет дополнительные проверки типов для проверки того, что тип метода SAM действительно совместим с экземпляром метода типа. В соответствии с документацией LambdaMetafactory::metafactory
экземплярный тип метода должен быть одинаковым или специализация типа метода SAM:
instantiatedMethodType - тип подписи и возвращаемого значения, который должен динамически применяться во время вызова. Это может быть то же самое, что и samMethodType, или может быть его специализацией.
который тип метода, сгенерированный ECJ, очевидно, не является, так что это приводит к исключению. (хотя, если честно, я не вижу нигде определенного определения, что представляет собой "специализацию" в этом случае). Я сообщил об этом в багзилле Eclipse здесь: https://bugs.eclipse.org/bugs/show_bug.cgi?id=546161
Я предполагаю, что это изменение было сделано где-то в JDK 9, так как исходный код на тот момент уже был модульным, а дата пересмотра довольно ранняя (февраль 2017 года).
Поскольку javac
генерирует правильную сигнатуру метода, вы можете переключиться на нее в качестве временного решения.