Кастинг Java: Java 11 выдает исключение LambdaConversionException, а 1.8 — нет.

следующий код отлично работает на виртуальной машине Java 1.8, но выдает LambdaConversionException при выполнении на виртуальной машине Java 11. В чем разница и почему он так себя ведет?


Код:

public void addSomeListener(Component comp){
    if(comp instanceof HasValue) {
        ((HasValue<?,?>) comp).addValueChangeListener(evt -> {
            //do sth with evt
        });
    }
}

HasValue Javadoc< /а>

Исключение (только V11):

Caused by: java.lang.invoke.LambdaConversionException: Type mismatch
for instantiated parameter 0: class java.lang.Object is not a subtype
of interface com.vaadin.flow.component.HasValue$ValueChangeEvent
    at java.base/java.lang.invoke.AbstractValidatingLambdaMetafactory.checkDescriptor(AbstractValidatingLambdaMetafactory.java:308)
    at java.base/java.lang.invoke.AbstractValidatingLambdaMetafactory.validateMetafactoryArgs(AbstractValidatingLambdaMetafactory.java:294)
    at java.base/java.lang.invoke.LambdaMetafactory.altMetafactory(LambdaMetafactory.java:503)
    at java.base/java.lang.invoke.BootstrapMethodInvoker.invoke(BootstrapMethodInvoker.java:138)
    ... 73 more

Обходной путь:

ValueChangeListener<ValueChangeEvent<?>> listener = evt -> {
    // do sth with evt
};
((HasValue<?,?>) comp).addValueChangeListener(listener);

Система:
ОС: Windows 10
IDE: Eclipse 2018-12 (4.10.0)
Java (компиляция): ecj
Java (веб-сервер): JDK 11.0.2
Веб-сервер: Wildfly 15


person Gerrit Sedlaczek    schedule 05.04.2019    source источник
comment
Интересная и все же бедная вещь там (HasValue<?,?>) comp ... Такой проверки, хотя и согласованной, не было в коде JDK-8.   -  person Naman    schedule 05.04.2019
comment
Когда именно вы получаете это исключение? Когда вы добавляете слушателя или когда слушатель вызывается с событием? Если последнее, как вы вызываете его с помощью события (покажите кодовое место)   -  person Erwin Bolwidt    schedule 05.04.2019
comment
@ErwinBolwidt Это происходит, когда должен быть добавлен слушатель.   -  person Gerrit Sedlaczek    schedule 05.04.2019
comment
Хм... Я создал небольшой пример приветствия, используя официальный стартовый проект vaadin. Я добавил ваш метод и добавил TextField, который я передаю вашему методу addSomeListener. Он работает без указанной ошибки, и при изменении значения TextField добавленный слушатель выполняется, как и ожидалось. (Протестировано с помощью причала и Java 11: jetty-9.4.11.v20180605; built: 2018-06-05T18:24:03.829Z; git: d5fc0523cfa96bfebfbda19606cad384d772f04c; jvm 11.0.1+13)   -  person codinghaus    schedule 05.04.2019
comment
Какой компилятор вы используете? Это похоже на проблему с сгенерированным байт-кодом.   -  person Jorn Vernee    schedule 05.04.2019
comment
Это важная информация, так как Eclipse имеет свой собственный компилятор, поэтому проблемы, вызванные компилятором (и это очень похоже на проблему с компилятором), не должны тогда относиться к javac.   -  person Holger    schedule 05.04.2019
comment
Вы можете проверить разницу в байт-коде, проверив сгенерированный байт-код с помощью javap -c <classname>. Поскольку компилятор не должен изменяться, если вы также не меняете версии Eclipse, это, вероятно, вызвано усилением проверки типов BSM в JDK 11 (т. е. в коде, который создает экземпляр лямбда), который выявляет ошибку в ECJ.   -  person Jorn Vernee    schedule 05.04.2019
comment
@JornVernee изменение уровня языка иногда вызывает изменения в поведении компилятора.   -  person Holger    schedule 05.04.2019
comment
@Holger Да, ты прав. Я забыл об этом.   -  person Jorn Vernee    schedule 05.04.2019


Ответы (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. (revision) (к сожалению, это похоже на частный билет.), который добавляет дополнительные проверки типа, чтобы убедиться, что тип метода SAM действительно совместим с типом метода создания экземпляра. Согласно документации LambdaMetafactory::metafactory тип инстанцируемого метода должен быть таким же или специализации типа метода SAM:

instanceiatedMethodType — подпись и тип возвращаемого значения, которые должны применяться динамически во время вызова. Это может быть то же самое, что и samMethodType, или его специализация.

тип метода, сгенерированный ECJ, очевидно, не является, поэтому это приводит к возникновению исключения. (хотя, честно говоря, я нигде не вижу определения, что представляет собой «специализацию» в данном случае). Я сообщил об этом здесь: https://bugs.eclipse.org/bugs/show_bug.cgi?id=546161

Я предполагаю, что это изменение было сделано где-то в JDK 9, поскольку исходный код уже был модульным на тот момент, а дата пересмотра довольно ранняя (февраль 2017 г.).

Поскольку javac генерирует правильную сигнатуру метода, вы можете временно переключиться на нее в качестве обходного пути.

person Jorn Vernee    schedule 05.04.2019
comment
Я склонен чрезмерно усложнять эти ответы, связанные с байт-кодом (будучи тем, кто привык смотреть на байт-код). Трудно найти правильный баланс между достаточным объяснением и кратким ответом. Если что-то непонятно, пожалуйста, не стесняйтесь обращаться за разъяснениями. - person Jorn Vernee; 05.04.2019
comment
это не слишком сложно, и это простой для чтения и понимания ответ, лучшего ответа просто нет. С удовольствием прочитал, спасибо за потраченное время - person Eugene; 05.04.2019
comment
Не могли бы вы добавить ссылку на соответствующую ошибку Eclipse? - person howlger; 05.04.2019
comment
@JornVernee Вовсе нет. Я думаю, вы очень хорошо объясняете ответы, связанные с байт-кодом. - person Michael Berry; 05.04.2019
comment
@howlger Я не могу найти, похоже, его еще нет. - person Jorn Vernee; 05.04.2019
comment
@JornVernee Если он еще не существует, сообщите об этом в Eclipse и добавьте ссылку на ошибку в свой ответ. - person howlger; 05.04.2019
comment
Евгений, MichaelBerry Спасибо за поддержку, кстати ;) - person Jorn Vernee; 05.04.2019
comment
@JornVernee Спасибо за анализ этой проблемы, за подробное описание и за сообщение о ней в Eclipse. - person howlger; 05.04.2019
comment
Спасибо за объяснение проблемы. - person Gerrit Sedlaczek; 05.04.2019