使用Lambda方法简化事件并不适用于Java

时间:2016-04-02 15:16:38

标签: java javafx lambda java-8 functional-interface

我正在尝试使用JavaFX在Java 8中提供更简单的EventHandler<ActionEvent>版本。

最终版本应该看起来像

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

有了这个,我试图以更简单的方式创建事件处理程序:他们不会将事件作为参数,他们关心隐藏自己。

为了演示,我创建了一个测试套件来重现我遇到的问题:

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

我预计会发生以下情况:

  • 如果我使用处理程序的基本版本调用callIt()handle(),则会照常调用它。
  • 如果我使用&#34;专用&#34;,简化版本的处理程序类调用它们,我希望它能够转移对我给出的简化版本的调用。

这只能部分起作用:

  • 使用Simple / Complicated组合,它可以正常工作:调用complicated(int)的{​​{1}}方法打印给定的参数,然后调用Simple方法,反过来表示为lambda。
  • 然而,我所追求的组合是将simple()表示为lambda(可能甚至是空的),形成EventHandler<ActionEvent>,其Clickable称之为handle()。 (请不要对名称感到困惑;这是一个非常具有历史意义的界面,我只会改进,但不会完全改变。)在这种情况下,它不起作用。

这是我得到的堆栈跟踪:

onClick()

为什么它不起作用?

(如果我对这种情况不够清楚,请告诉我。)

1 个答案:

答案 0 :(得分:1)

通常,您不应该使用普通的Java操作激发AbstractMethodError(或任何类型的LinkageError)。所以遇到它通常是一个标志,一个破坏的编译器或一个不兼容的变化的环境,即一个类链接到一个不同版本的类,而不是它在编译时看到的。

我们在此处看到的是interface中缺少的桥接方法。当泛型类型通过可更新类型或使用不同下限重新声明类型变量的类型进行扩展时,继承方法的原始类型签名可能会更改,并且需要具有旧原始签名的桥接方法并委托给具有该方法的方法。新的签名。

在您的代码中,可重新生成的类型Clickable扩展了泛型类型EventHandler<ActionEvent>,并且在字节代码级别有两个方法,void handle(ActionEvent)void handle(Event),后者需要委托前者的桥梁方法。

从Java 8开始,这些桥接方法在interface中实现(现在可以使用非abstract方法),消除了从所有实现类实现它的负担(大大简化了过程)为lambda表达式生成类。)

从堆栈跟踪中我们可以看到方法BastelTest.handle正试图在编译时类型为handle的实例上调用EventHandler<ActionEvent>方法,该类型将以原始方式结束方法handle(Ljavafx/event/Event;)V,但lambda实例缺少必需的桥接方法,这意味着它应该从它继承它的接口中丢失。

对于像你这样的测试套件,所有链接类都是嵌套类,因此编译在一起,不太可能得到不匹配的版本(虽然不是不可能)。另一种可能性是您使用的是带有bug 436350, “Missing bridge method in interface results in AbstractMethodError”的旧Eclipse版本。