صب Java: Java 11 يلقي LambdaConversionException بينما 1.8 لا



eclipse java-8 (1)

TL؛ DR ينشئ مترجم Eclipse توقيع طريقة لمثيل lambda غير صالح وفقًا للمواصفات. بسبب رمز التحقق من الكتابة الإضافي الذي تم إضافته في 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 ) (للأسف يبدو أن هذا هو تذكرة خاصة.) الذي يضيف JDK-8173587 نوع إضافية للتحقق من أن نوع SAM SAM متوافق بالفعل مع نوع الأسلوب إنشاء مثيل. وفقًا لتوثيق LambdaMetafactory::metafactory ، يجب أن يكون نوع الطريقة التي تم إنشاء مثيل لها هو نفسه ، أو أحد تخصصات نوع طريقة SAM:

instantiatedMethodType - نوع التوقيع والإرجاع الذي يجب فرضه ديناميكيًا في وقت الاحتجاج. قد يكون هذا هو نفس samMethodType ، أو قد يكون تخصصًا له.

التي من الواضح أن نوع الأسلوب الذي تم إنشاؤه بواسطة ECJ ، لذلك ينتهي الأمر بطرح استثناء. (على الرغم من ذلك ، لكي نكون منصفين ، لا أرى في أي مكان محددًا ما الذي يشكل "تخصصًا" في هذه الحالة). لقد أبلغت عن هذا عن bugzilla Eclipse هنا: https://bugs.eclipse.org/bugs/show_bug.cgi?id=546161

أعتقد أن هذا التغيير قد تم في مكان ما في JDK 9 ، نظرًا لأن شفرة المصدر كانت بالفعل معيارية في تلك المرحلة ، وكان تاريخ المراجعة مبكرًا إلى حد ما (فبراير 2017).

نظرًا لأن javac يولد توقيع الطريقة الصحيحة ، يمكنك التبديل إلى ذلك في الوقت الحالي كحل بديل.

تعمل التعليمة البرمجية التالية بشكل جيد تمامًا في Java 1.8 VM ولكنها تنتج LambdaConversionException عند تنفيذها في Java 11 VM. أين الاختلاف ولماذا يتصرف مثل هذا؟

الشفرة:

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

HasValue Javadoc

استثناء (الإصدار 11 فقط):

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);

النظام:
نظام التشغيل: ويندوز 10
IDE: Eclipse 2018-12 (4.10.0)
جافا (ترجمة): ecj
جافا (خادم الويب): JDK 11.0.2
خادم الويب: Wildfly 15





vaadin-flow