2015-11-25 4 views
19

JDK ist Oracle 'JDK 1.8u65, aber das Problem wurde mit "so niedrig wie" 1.8u25 auch gesehen. HierEnum, Schnittstellen und (Java 8) Lambdas: Code kompiliert, schlägt aber zur Laufzeit fehl; wird das erwartet?

ist die volle SSCCE:

public final class Foo 
{ 
    private interface X 
    { 
     default void x() 
     { 
     } 
    } 

    private enum E1 
     implements X 
    { 
     INSTANCE, 
     ; 
    } 

    private enum E2 
     implements X 
    { 
     INSTANCE, 
     ; 
    } 

    public static void main(final String... args) 
    { 
     Stream.of(E1.INSTANCE, E2.INSTANCE).forEach(X::x); 
    } 
} 

Dieser Code kompiliert; aber es schlägt zur Laufzeit:

Exception in thread "main" java.lang.BootstrapMethodError: call site initialization exception 
    at java.lang.invoke.CallSite.makeSite(CallSite.java:341) 
    at java.lang.invoke.MethodHandleNatives.linkCallSiteImpl(MethodHandleNatives.java:307) 
    at java.lang.invoke.MethodHandleNatives.linkCallSite(MethodHandleNatives.java:297) 
    at com.github.fge.grappa.debugger.main.Foo.main(Foo.java:38) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
    at java.lang.reflect.Method.invoke(Method.java:497) 
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144) 
Caused by: java.lang.invoke.LambdaConversionException: Invalid receiver type class java.lang.Enum; not a subtype of implementation type interface com.github.fge.grappa.debugger.main.Foo$X 
    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:302) 
    ... 8 more 

Es in Code zu reparieren ist "einfach"; im Hauptverfahren, müssen Sie nur noch auf:

// Note the <X> 
Stream.<X>of(E1.INSTANCE, E2.INSTANCE).forEach(X::x); 

EDIT in der Tat gibt es eine zweite Art und Weise, wie sie in der akzeptierten Antwort erwähnt ... Ersetzen Sie die Methode Referenz mit einem Lambda:

Stream.of(E1.INSTANCE, E2.INSTANCE).forEach(x -> x.x()); 

Also, uh. was geschieht hier? Warum kompiliert der ursprüngliche Code überhaupt? Ich hätte erwartet, dass der Compiler bemerkt, dass die Methodenreferenz nicht auf irgendwas war Enum<?>, aber auf X, aber nein ...

Was vermisse ich? Ist das ein Fehler im Compiler? Ein Missverständnis von mir?

+2

Vielleicht ein verwandter 'javac'-Fehler: [JDK-8141508] (https://bugs.openjdk.java.net/browse/JDK-8141508), obwohl er sich mit sich überschneidenden Typen zu befassen scheint. – Tunaki

+0

@Tunaki interessant; Ich werde dieses Problem durchlesen, vielleicht ist es das gleiche ... – fge

+2

Es * ist * ein sich überschneidender Typ. Der abgeleitete Typ von "Stream.of (E1.INSTANCE, E2.INSTANCE)" ist laut meiner Eclipse "Stream & Foo.X>". – RealSkeptic

Antwort

15

Es scheint, dass Sie JDK-8141508 getroffen haben, was in der Tat ein Fehler von javac beim Umgang mit Schnitttypen und Methodenreferenzen ist. Es wird festgelegt, in Java 9.

Zitiert a mail from Remi Forax fixiert werden:

Javac hat mit Kreuzungstyp Probleme, die Zielart und einer Lambda-Methode Referenz, Normalerweise, wenn es einen Kreuzungstyp, Javac Ersetzen Sie es durch den ersten Typ des Schnitttyps und fügen Sie bei Bedarf eine Besetzung hinzu.

Lassen wir annehmen, diesen Code haben,

public class Intersection { 
     interface I { 
     } 
     interface J { 
      void foo(); 
     } 

     static <T extends I & J> void bar(T t) { 
      Runnable r = t::foo; 
     } 

     public static void main(String[] args) { 
      class A implements I, J { public void foo() {} } 
      bar(new A()); 
     } 
    } 

Derzeit erzeugt Javac ein Verfahren Bezug auf J :: foo mit einem invokedynamic, die ein I als Parameter übernimmt, also nicht zur Laufzeit. javac sollte t :: foo in ein Lambda entzuckerten, das ein I nimmt, und dann einen Cast zu J hinzufügen, wie für einen Aufruf einer Methode eines Schnitttyps.

So ist die Abhilfe stattdessen eine Lambda zu verwenden,

Runnable r = t -> t.foo(); 

Ich habe bereits diesen Fehler irgendwo gesehen, aber war nicht in der Lage einen entsprechenden Fehlerbericht in der Datenbank zu finden :(

In Ihrem Code ist der von Stream.of(E1.INSTANCE, E2.INSTANCE) erstellte Stream vom Typ Stream<Enum<?>&Foo.X>, der alle Elemente des Fehlers kombiniert: sich überschneidende Typen und Methodenreferenzen.

As-Nr ted durch Remi Forax wäre eine Behelfslösung:

Stream.of(E1.INSTANCE, E2.INSTANCE).forEach(x -> x.x()); 

d.h. einen expliziten Lambda-Ausdruck anstelle einer Verfahrens-Referenz.

+0

Ja, diese Problemumgehung funktioniert auch (ich habe es getestet, bevor du geantwortet hast und bist auch auf diese Mail gestoßen, nachdem du mit dem Fehler verlinkt hast). Gut graben! Vielen Dank! – fge

+0

Ich öffnete eine Erweiterungsanfrage auf Jetbrains dafür: https://youtrack.jetbrains.com/issue/IDEA-148528. Ich denke, eine ähnliche Anfrage könnte für Eclipse geöffnet werden :) – fge

+0

@fge Ja, ich habe ihre Bugzilla gesucht, konnte aber nichts finden. – Tunaki

Verwandte Themen