2015-11-25 14 views
19

JDK è Oracle JDK 1.8u65 ma il problema è stato riscontrato anche con "basso" come 1.8u25.Enum, interfacce e (Java 8) lambda: il codice viene compilato ma non riesce in fase di runtime; è previsto?

Ecco la SSCCE completo:

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

Questo codice viene compilato; ma non riesce in fase di esecuzione:

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 

Il fissaggio nel codice è "facile"; nel metodo principale, devi solo:

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

EDIT V'è infatti un secondo modo, come menzionato nella risposta accettata ... sostituire il riferimento metodo con un lambda:

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

Quindi, uh. Che succede qui? Perché il codice iniziale viene compilato in primo luogo? Mi sarei aspettato che il compilatore notasse che il riferimento al metodo non era su qualsiasi cosa Enum<?> ma su X, ma no ...

Cosa mi manca? È un bug nel compilatore? Un mio fraintendimento?

+2

Forse un bug 'javac' correlato: [JDK-8141508] (https://bugs.openjdk.java.net/browse/JDK-8141508) anche se sembra che si tratti di tipi intersecanti. – Tunaki

+0

@Tunaki interessante; Leggerò questo problema, forse è lo stesso ... – fge

+2

È * un * tipo di intersezione. Il tipo inferito di 'Stream.of (E1.INSTANCE, E2.INSTANCE)' è 'Stream & Foo.X>', secondo la mia Eclipse. – RealSkeptic

risposta

15

Sembra che tu abbia colpito JDK-8141508, che è effettivamente un bug di javac quando si tratta di tipi di intersezione e riferimenti di metodo. È pianificato per essere risolto in Java 9.

Citando a mail from Remi Forax:

javac ha problemi con tipo di intersezione che sono tipo di destinazione di un riferimento lambda e metodo, solito quando v'è un tipo di intersezione, javac sostituirlo con il primo tipo del tipo di intersezione e aggiungere cast, se necessario.

lasciare supporre che abbiamo questo codice,

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

Attualmente, javac genera un riferimento metodo su J :: foo con un'invokedynamic che accetta un io come parametro, e quindi non riesce a runtime. javac dovrebbe de-sugar t :: foo in una lambda che prende un I e quindi aggiunge un cast a J come per una chiamata a un metodo di un tipo di intersezione.

Quindi la soluzione è quella di utilizzare un lambda invece,

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

ho già visto questo bug da qualche parte, ma non è stato in grado di trovare una corrispondente segnalazione di bug nel database :(

Nel tuo codice, il flusso creato da Stream.of(E1.INSTANCE, E2.INSTANCE) è di tipo Stream<Enum<?>&Foo.X>, che combina tutti gli elementi del bug: tipi intersecanti e riferimenti metodo

Come no ta da Remi Forax, una soluzione alternativa sarebbe:

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

cioè utilizzando un'espressione lambda esplicita invece di un metodo di riferimento.

+0

Sì, questa soluzione funziona anche (l'ho testata prima che tu rispondessi e incappassi anche in questa stessa mail, dopo che ti sei collegato al bug). Buona scavatura! Grazie! – fge

+0

Ho aperto una richiesta di miglioramento su Jetbrains per questo: https://youtrack.jetbrains.com/issue/IDEA-148528. Immagino che una richiesta simile possa essere aperta per Eclipse :) – fge

+0

@fge Sì, ho cercato il loro Bugzilla ma non ho trovato nulla. – Tunaki

Problemi correlati