2016-04-02 6 views
0

Ich versuche, eine einfachere Version von EventHandler<ActionEvent> in Java 8 mit JavaFX bereitzustellen.Simplify Ereignis mit einer Lambda-Methode funktioniert nicht in Java

Die endgültige Version aussehen

package dialogutil; 

import org.controlsfx.dialog.Dialog; 

import javafx.event.ActionEvent; 
import javafx.event.EventHandler; 

@FunctionalInterface 
public interface Clickable extends EventHandler<ActionEvent> { 

    public static Clickable EMPTY =() -> {}; 

    public void onClick(); 

    @Override 
    public default void handle(ActionEvent event) { 
     this.onClick(); 
     if (event != null && event.getSource() != null) { 
      ((Dialog)event.getSource()).hide(); 
     } 
    } 
} 

Damit soll, ich versuche, Event-Handler auf einfachere Art und Weise zu schaffen: sie das Ereignis als Parameter nicht nehmen, und sie kümmern sich um Versteck sich.

Aus Gründen der Demonstration, ich eine Testsuite erstellt das Problem, das ich mit, dass haben zu reproduzieren:

package test; 

import javafx.event.ActionEvent; 
import javafx.event.EventHandler; 

import org.junit.Test; 

public class BastelTest { 

    /** 
    * Interface Complicated is called with a value. 
    */ 
    @FunctionalInterface 
    interface Complicated { 
     void complicated(int value); 
    } 


    /** 
    * Interface Simple is called without a value. 
    */ 
    @FunctionalInterface 
    interface Simple extends Complicated { 
     void simple(); 
     /** 
     * The value given is printed and then the call is deflected to the simple method given. 
     */ 
     @Override 
     default void complicated(int value) { 
      System.out.println("Swallowing the " + value); 
      simple(); 
     } 
    } 

    /** 
    * This is in order to try the Complicated/Simple interface. 
    * The given {@link Complicated} is called with a 42. 
    * It can be a {@link Simple} as well; in this case the call is deflected. 
    * @param x 
    */ 
    private void callIt(Complicated x) { 
     x.complicated(42); 
    } 

    /** 
    * This is the interface I am indeed working on. 
    * Here the deflection doesn't work; instead, I get an AbstractMethodError. 
    */ 
    @FunctionalInterface 
    public interface Clickable extends EventHandler<ActionEvent> { 

     public static Clickable EMPTY =() -> {}; 

     public void onClick(); 

     @Override 
     public default void handle(ActionEvent event) { 
      System.out.println("Simplifying the call:"); 
      this.onClick(); 
      System.out.println("Call simplified."); 
     } 

    } 

    private void handle(EventHandler<ActionEvent> x) { 
     System.out.println("Handling null event via " + x); 
     x.handle(null); 
     System.out.println("Handling nonnull event via " + x); 
     x.handle(new ActionEvent()); 
    } 


    @Test 
    public void testFunc() { 
     callIt(x -> System.out.println("Complicated with " + x)); 
     callIt((Simple)() -> System.out.println("Called simple.")); 

     Clickable c =() -> System.out.println("Hdl3"); 
     c.handle(null); 

     handle(x -> System.out.println("Hdl1 " + x)); 
     handle((Clickable)() -> System.out.println("Hdl2")); 
     handle(Clickable.EMPTY); 
    } 
} 

Hier folgendes passieren Ich erwarte, dass:

  • Wenn ich callIt() anrufen oder handle() mit der Basisversion des Handlers, wird es wie üblich aufgerufen.
  • Wenn ich sie mit der "spezialisierten", vereinfachten Version der Handler-Klasse anrufe, erwarte ich, dass sie diesen Aufruf auf die vereinfachte Version umlenkt, die ich gebe.

Dies funktioniert nur teilweise:

  • mit der Kombination Simple/Complicated, es funktioniert: das gegebene Argument der complicated(int) Verfahren zur Herstellung eines Simple druckt ruft und ruft dann die simple() Methode, die wiederum ausgedrückt als ein Lambda.
  • Die Kombination ich nach bin, ist jedoch eine EventHandler<ActionEvent> als Lambda-exprimierenden (möglicherweise sogar leer), die diese onClick() ein Clickable deren handle() Anrufe bildet. (Bitte seien Sie nicht verwirrt über die Namen; es ist eine ziemlich historische Schnittstelle, die ich nur verbessern werde, aber nicht vollständig ändern.) In diesem Fall funktioniert es nicht.

Hier wird der Stack-Trace ich:

java.lang.AbstractMethodError: Method test/BastelTest$$Lambda$7.handle(Ljavafx/event/Event;)V is abstract 
    at test.BastelTest$$Lambda$7/25282035.handle(Unknown Source) 
    at test.BastelTest.handle(BastelTest.java:38) 
    at test.BastelTest.testFunc(BastelTest.java:69) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 
    at [usual test case stuff...] 

Warum es nicht funktioniert?

(Wenn ich nicht klar genug über die Lage bin, mir bitte sagen.)

+1

Ich kann Ihren Fehler mit JDK 1.8.0_74 nicht reproduzieren. Welche Version hast du? Auch das ist nicht verwandt, aber 'Interface Simple extends Complicated' sieht wirklich komisch aus (wenn etwas einfach ist, dann ist es kompliziert?) – Tunaki

+0

Unrelated. 'public static Clickable.EMPTY' - Variablen in' interface's sind nicht erlaubt, 'EMPTY' ist implizit' public static final'. 'public void onClick()' hier 'public' ist überflüssig. –

+0

Ich habe 1.8.0_20. Gute Idee - Ich sollte aktualisieren, um zu sehen, ob es dann funktioniert ... – glglgl

Antwort

1

Im Allgemeinen sollten Sie nicht in der Lage sein, eine AbstractMethodError (oder irgendeine Art von LinkageError) unter Verwendung von gewöhnlichen Java-Operationen zu provozieren. Daher ist es normalerweise ein Zeichen für einen fehlerhaften Compiler oder eine inkompatibel geänderte Umgebung, d. H. Eine Klasse ist mit einer anderen Version einer Klasse verknüpft, als sie zur Kompilierungszeit gesehen hat.

Was wir hier sehen, ist eine Bridge-Methode in einem interface fehlt. Wenn ein generischer Typ entweder um einen verifizierbaren Typ oder einen Typ erweitert wird, der eine Typvariable unter Verwendung einer anderen unteren Grenze neu deklariert, kann sich die Rohtypsignatur geerbter Methoden ändern und eine Bridge-Methode mit der alten Rohsignatur erfordern und an die Methode delegieren die neue Unterschrift.

In Code, der reifiable Typ Clickable erweitert die gattungsgemäßen Art EventHandler<ActionEvent> und hat zwei Methoden an der Byte-Code-Ebene, und void handle(ActionEvent)void handle(Event), wobei letztere eine Brückenmethode erfordert die früheren delegieren.

Beginnend mit Java 8 sind diese Bridge-Methoden in interface implementiert (jetzt sind keine Methoden möglich), wodurch die Implementierung aus allen Implementierungsklassen entfällt .

Aus dem Stack-Trace wir, dass das Verfahren für BastelTest.handle versucht, die handle Methode auf einer Instanz mit dem Kompilierung-Typ EventHandler<ActionEvent> aufzurufen, die an der Roh-Methode werden am Ende handle(Ljavafx/event/Event;)V, aber die erforderliche Brückenmethode fehlt sehen die Lambda-Instanz, was impliziert, dass sie auch in der Schnittstelle fehlt, von der sie vererbt werden soll.

Für eine Testsuite wie die Ihre, in der alle verknüpften Klassen verschachtelte Klassen sind und daher zusammen kompiliert werden, ist es unwahrscheinlich, dass sie nicht übereinstimmende Versionen erhalten (wenn auch nicht unmöglich). Die andere Möglichkeit ist, dass Sie eine ältere Eclipse-Version mit der bug 436350, “Missing bridge method in interface results in AbstractMethodError” verwenden.

+0

Wow, vielen Dank! Während ich mich nun in Richtung a) bewegt habe, indem ich einen etwas anderen Ansatz gewählt habe und b) an anderen Stellen im Code gearbeitet habe, schätze ich diese sehr ausführliche Antwort sehr. Aus Zeitgründen kann ich derzeit nicht testen, ob Eclipse "schuldig" ist, aber ich werde es mir so bald wie möglich ansehen - wahrscheinlich nächste Woche. – glglgl